C++ Core Guidelines: More Rules for Expressions


I know the headline to this post is a little bit boring: More Rules for Expressions. Honestly, this post is about code hygiene, because I will mainly write about pointers.



Let's have a look at my plan for today.

I will start with a very import rule.

ES.42: Keep use of pointers simple and straightforward

Let me cite the words of the guidelines: "Complicated pointer manipulation is a major source of errors.". Why should we care? Of course, our legacy code is full of functionality such as this example:

void f(int* p, int count)
    if (count < 2) return;

    int* q = p + 1;    // BAD

    int n = *p++;      // BAD

    if (count < 6) return;

    p[4] = 1;          // BAD

    p[count - 1] = 2;  // BAD

    use(&p[0], 3);     // BAD

int myArray[100];     // (1)

f(myArray, 100),      // (2)


The main issue with this code is that the caller must provide the correct length of the C-array. If not, we have undefined behaviour.

Think about the last lines (1) and (2) for a few seconds. We start with an array and remove its type information by passing it to the function f. This process is called an array to pointer decay and is the reason for a lot of errors. Maybe we had a bad day and we count the number of elements wrong or the size of C-array changed. Anyway, the result is always the same: undefined behaviour. The same argumentation will also hold for a C-string.

What should we do? We should use the right data type. The Guidelines suggests using gsl::spantype from the Guidelines Support Library (GSL). Have a look here:

void f(span<int> a) // BETTER: use span in the function declaration
    if (a.length() < 2) return;

    int n = a[0];      // OK

    span<int> q = a.subspan(1); // OK

    if (a.length() < 6) return;

    a[4] = 1;          // OK

    a[count - 1] = 2;  // OK

    use(a.data(), 3);  // OK


Fine! gsl::span checks at run-time its boundaries. Additionally, the Guidelines Support Library has a free function at for accessing the elements of an gsl::span. 

void f3(array<int, 10> a, int pos) 
    at(a, pos / 2) = 1;       // OK
    at(a, pos - 1) = 2;       // OK


I know your issue. Most of you don't use the Guidelines Support Library. No problem. It's quite easy to rewrite the functions f and f3 using the container std::array and the method std::array::at. Here we are:

// spanVersusArray.cpp

#include <algorithm>
#include <array>

void use(int*, int){}

void f(std::array<int, 100>& a){

    if (a.size() < 2) return;

    int n = a.at(0);      

    std::array<int, 99> q;
    std::copy(a.begin() + 1, a.end(), q.begin());      // (1)

    if (a.size() < 6) return;

    a.at(4) = 1;          

    a.at(a.size() - 1) = 2;

    use(a.data(), 3); 

void f3(std::array<int, 10> a, int pos){
    a.at(pos / 2) = 1;      
    a.at(pos - 1) = 2; 

int main(){

    std::array<int, 100> arr{};

    std::array<int, 10> arr2{};
    f3(arr2, 6);



The std::array::at Operator will check at runtime its bounds. If pos >= size(), you will get an std::out_of_range exception. If you look carefully at the spanVersusArray.cpp program, you will notice two issues. First, the expression (1) is more verbose than the gsl::span version and second, the size of the std::array is part of the signature of the function f. This is really bad. I can only use f with the type std::array<int, 100>.  In this case, the checks of the array size inside the function are superfluous. 

To your rescue, C++ has templates; therefore, it's easy to overcome the type restrictions but staying type-safe.


// at.cpp

#include <algorithm>
#include <array>
#include <deque>
#include <string>
#include <vector>

template <typename T>
void use(T*, int){}

template <typename T>
void f(T& a){

    if (a.size() < 2) return;

    int n = a.at(0);      

    std::array<typename T::value_type , 99> q;                 // (4)
    std::copy(a.begin() + 1, a.end(), q.begin());     

    if (a.size() < 6) return;

    a.at(4) = 1;          

    a.at(a.size() - 1) = 2;

    use(a.data(), 3);                                          // (5)

int main(){

    std::array<int, 100> arr{};                                             
    f(arr);                                                    // (1)
    std::array<double, 20> arr2{};
    f(arr2);                                                   // (2)
    std::vector<double> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
    f(vec);                                                    // (3)
    std::string myString= "123456789";
    f(myString);                                               // (4)
    // std::deque<int> deq{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // f(deq);                                                 // (5)


Now, the function f works for std::array's of different sizes and types (lines (1) and (2)) but also for an std::vector(3) or an std::string (4). This container have in common, that their data is stored in a continuous memory block. This will no hold std::deque; therefore, the call a.data() in expression (5) fails. An std::deque is a kind of doubly linked list of small memory blocks.


The expression T::value_type (5) helps me to get the underlying value type of each container. T is a so-called dependent type because T is a type parameter of the function template f. This is the reason, I have to give the compiler a hint that T::value_type is actually a type: typename T::value_type.

ES.45: Avoid “magic constants”; use symbolic constants

This is obvious: A symbolic constant says more than a magic constant. 

The guidelines starts with a magic constant, continuous with a symbolic constant, and finishes with a range-based for loop. 

for (int m = 1; m <= 12; ++m)        // don't: magic constant 12
    cout << month[m] << '\n';

                  // months are indexed 1..12 (symbolic constant)
constexpr int first_month = 1;
constexpr int last_month = 12;

for (int m = first_month; m <= last_month; ++m)        // better
    cout << month[m] << '\n';

for (auto m : month)          // the best (ranged-based for loop)
    cout << m << '\n';


In the case of the ranged-based for loop, it is not possible to make an off-by-one error.  

Let me directly jump to the rule ES.47. I want to put the rules for conversion including ES.46 in a separate post.

ES.47: Use nullptr rather than 0 or NULL

There are a lot of reasons to use a nullptr instead the number 0 or the macro NULL. In particular, 0 or NULL will not work in generic. I have already written a post about this three kinds of null pointer. Here are the details: The Null Pointer Constant nullptr.

What's next?

How many explicit casts do we have in modern C++? Maybe your number is 4 but this is the wrong number. In C++11 we have 6 explicit cast. When I Include the GSL, we have 8 explicit casts. I will write about the 8  casts in the next post.




Thanks a lot to my Patreon Supporters: Eric Pederson, Paul Baxter, Carlos Gomes Martinho, and SAI RAGHAVENDRA PRASAD POOSA.



Get your e-book at leanpub:

The C++ Standard Library


Concurrency With Modern C++


Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more the 100 source files.


Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 550 pages full of modern C++ and more than 100 source files presenting concurrency in practice.


Add comment

My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code


Today 336

All 641693

Currently are 270 guests and no members online