In the last two weeks I have written about the basics of compile time constants and calculation with those constants. This week I conclude this mini series with the keyword `constexpr` added in C++11/14.

Limits of C++03 compile time calculations

The calculations I described in the last post were either rather simple or involved template meta functions. Non-template calculations are essentially limited to one-liners. In addition, we can not reuse them but have to copy-paste them everywhere we have to do the same or similar calculations.

Template meta functions on the other hand can be extremely complicated to read. With C++14 we got variable templates which can improve the readability of template meta functions a bit. Nevertheless there is a remaining issue.

All those compile time calculations can be used solely at compile time. If we want to do the same calculation at run time, we have to duplicate the code. Since we can’t use templates or don’t want to copy-paste those one-liners around, the run time calculations will look different to the compile time calculations, which makes it hard to spot differences.

constexpr to the rescue

So what if there were functions that can be executed at compile time and run time, depending on the context? That kind of function was introduced in C++11. They are simply functions marked with the keyword `constexpr`.

With C++11 `constexpr`, the template meta programming Fibonacci function shown in the last post would be implemented like this:

constexpr unsigned fibonacci(unsigned i) { return (i <= 1u) ? i : (fibonacci(i-1) + fibonacci(i-2)); }

We can now use this function as well in a compile time context as during run time. Naturally, we can only use it at compile time if the provided arguments themselves are compile time constants. The compiler still has no crystal ball to know which values a run time argument might have.

int main(int argc, char** argv) { char int_values[fibonacci(6)] = {}; //OK, 6 is a compile time constant std::cout << sizeof(int_values) << '

'; //8 std::cout << fibonacci(argc) << '

'; //OK, run time calculation std::cout << sizeof(std::array<char, fibonacci(argc)>) << '

'; //ERROR }

The last line is an error because, since `argc` is not a compile time constant, neither is `fibonacci(argc)`.

constexpr variables and literal types

Variables that are declared `constexpr` are, as the keyword suggests, constant expressions and can be used for compile time calculations. Unlike in C++03, where only literals of built in type could be compile time constants, the restriction has been relaxed in C++11 and C++14.

The category of types that can be used for `constexpr` variables is called literal type. Most notably, literal types include classes that have `constexpr` constructors, so that values of the type can be initialized calling `constexpr` functions.

Consider for example this point class which is a literal type:

class Point { int x; int y; public: constexpr Point(int ix, int iy) : x{ix}, y{iy} {} constexpr int getX() const { return x; } constexpr int getY() const { return y; } };

We can create `constexpr` variables from it, and since it has constexpr getters as well, use the values of those variables in compile time contexts:

constexpr Point p{22, 11}; constexpr int py = p.getY(); double darr[py] {};

constexpr functions

In C++11 there were pretty tight restrictions for the content of`constexpr` functions. Basically the function body was restricted to a single return statement, apart from optional `typedef`s, `static_assert`s etc.

In C++14 most of those restrictions are lifted. The most notable remaining restrictions are that there may be no try blocks and no variables of static or thread local storage. So, in C++14 the `fibonacci` function can be written in a more readable form:

constexpr unsigned fibonacci(unsigned i) { switch (i) { case 0: return 0; case 1: return 1; default: return fibonacci(i-1) + fibonacci(i-2); } }

Runtime functionality in `constexpr` functions

If we use a `constexpr` function at compile time, we are not only bound to arguments that are known at compile time. The function may also only call other `constexpr` functions and it is forbidden to use any stuff that needs a run time context, such as throwing exceptions, calling `new` or `delete` and similar things.

However, that does not mean we are not allowed to write these things in a `constexpr` function. We can do this, but we may not call it for compile time calculations in a way that would try to execute those lines.

The standard actually demands, that if the evaluation of a `constexpr` function call makes the evaluation of “run time constructs” (this is not official standard wording) necessary, that function call is not a constant expression any more.

The list of those run time constructs is rather long, it includes for example calls to non-`constexpr` functions, `new`, `delete`, `throw`, `reinterpret_cast`, and “expressions that would exceed implementation defined limits”. The latter basically means that we can not run programs of arbitrary length and complexity at compile time.

The key thing is however that a `constexpr` function call remains a constant expression if no run time construct needs to be evaluated. Let’s for example build a little check against integer overflow into our `fibonacci` function:

constexpr unsigned fibonacci(unsigned i) { switch (i) { case 0: return 0; case 1: return 1; default: { auto f1 = fibonacci(i-1); auto f2 = fibonacci(i-2); if (f1 > std::numeric_limits<unsigned>::max() - f2) { throw std::invalid_argument{"Argument would cause overflow"}; } return f1+f2; } } }

This check will always work, but in different ways. If we call the function with too large a value in a run time context, we will get the `std::invalid_argument` thrown at run time. If we call it in a compile time context with such a large argument, the compiler will simply tell us that the function call is not a constant expression.

Conclusion

Compile time calculations have become a nicely usable feature in C++14. While they do increase the time it takes to compile our code, they can reduce the execution time and memory footprint of our programs. Therefore look out for opportunities to use `constexpr` and measure if it can improve your run time statistics.

Some people even recommend to at least try to make every function a `constexpr` and let the compiler decide whether it can and will execute them at compile time or not. This may however be not feasible, since it would litter our code with those extra keywords while the benefits may be questionable.

Update: Read about the constexpr additions that came to language and library in C++17.