C++

constexpr

Introduction#

constexpr is a keyword that can be used to mark a variable’s value as a constant expression, a function as potentially usable in constant expressions, or (since C++17) an if statement as having only one of its branches selected to be compiled.

Remarks#

The constexpr keyword was added in C++11 but for a few years since the C++11 standard was published, not all major compilers supported it. at the time that the C++11 standard was published. As of the time of publication of C++14, all major compilers support constexpr.

constexpr variables

A variable declared constexpr is implicitly const and its value may be used as a constant expression.

Comparison with #define

A constexpr is type-safe replacement for #define based compile-time expressions. With constexpr the compile-time evaluated expression is replaced with the result. For example:

int main()
{
   constexpr int N = 10 + 2;
   cout << N;
}

will produce the following code:

cout << 12;

A pre-processor based compile-time macro would be different. Consider:

#define N 10 + 2

int main()
{
    cout << N;
}

will produce:

cout << 10 + 2;

which will obviously be converted to cout << 10 + 2;. However, the compiler would have to do more work. Also, it creates a problem if not used correctly.

For example (with #define):

cout << N * 2;

forms:

cout << 10 + 2 * 2; // 14

But a pre-evaluated constexpr would correctly give 24.

Comparison with const

A const variable is a variable which needs memory for its storage. A constexpr does not. A constexpr produces compile time constant, which cannot be changed. You may argue that const may also not be changed. But consider:

int main()
{
   const int size1 = 10;
   const int size2 = abs(10);

   int arr_one[size1]; 
   int arr_two[size2]; 
}

With most compilers the second statement will fail (may work with GCC, for example). The size of any array, as you might know, has to be a constant expression (i.e. results in compile-time value). The second variable size2 is assigned some value that is decided at runtime (even though you know it is 10, for the compiler it is not compile-time).

This means that a const may or may not be a true compile-time constant. You cannot guarantee or enforce that a particular const value is absolutely compile-time. You may use #define but it has its own pitfalls.

Therefore simply use:

int main()
{
    constexpr int size = 10;

    int arr[size];
}

A constexpr expression must evaluate to a compile-time value. Thus, you cannot use:

constexpr int size = abs(10);

Unless the function (abs) is itself returning a constexpr.

All basic types can be initialized with constexpr.

constexpr bool FailFatal = true;
constexpr float PI = 3.14f;
constexpr char* site= "StackOverflow";

Interestingly, and conveniently, you may also use auto:

constexpr auto domain = ".COM";  // const char * const domain = ".COM"
constexpr auto PI = 3.14;        // constexpr double

constexpr functions

A function that is declared constexpr is implicitly inline and calls to such a function potentially yield constant expressions. For example, the following function, if called with constant expression arguments, yields a constant expression too:

constexpr int Sum(int a, int b)
{
    return a + b;
}

Thus, the result of the function call may be used as an array bound or a template argument, or to initialize a constexpr variable:

int main()
{
    constexpr int S = Sum(10,20);
   
    int Array[S];
    int Array2[Sum(20,30)]; // 50 array size, compile time
}

Note that if you remove constexpr from function’s return type specification, assignment to S will not work, as S is a constexpr variable, and must be assigned a compile-time const. Similarly, size of array will also not be a constant-expression, if function Sum is not constexpr.

Interesting thing about constexpr functions is that you may also use it like ordinary functions:

int a = 20;
auto sum = Sum(a, abs(-20));

Sum will not be a constexpr function now, it will be compiled as an ordinary function, taking variable (non-constant) arguments, and returning non-constant value. You need not to write two functions.

It also means that if you try to assign such call to a non-const variable, it won’t compile:

int a = 20;
constexpr auto sum = Sum(a, abs(-20));

The reason is simple: constexpr must only be assigned a compile-time constant. However, the above function call makes Sum a non-constexpr (R-value is non-const, but L-value is declaring itself to be constexpr).


The constexpr function must also return a compile-time constant. Following will not compile:

constexpr int Sum(int a, int b)
{
    int a1 = a;     // ERROR
    return a + b;
}

Because a1 is a non-constexpr variable, and prohibits the function from being a true constexpr function. Making it constexpr and assigning it a will also not work - since value of a (incoming parameter) is still not yet known:

constexpr int Sum(int a, int b)
{
   constexpr int a1 = a;     // ERROR
   ..

Furthermore, following will also not compile:

constexpr int Sum(int a, int b)
{
   return abs(a) + b; // or abs(a) + abs(b)
}

Since abs(a) is not a constant expression (even abs(10) will not work, since abs is not returning a constexpr int !

What about this?

constexpr int Abs(int v)
{
    return v >= 0 ? v : -v;
}

constexpr int Sum(int a, int b)
{
    return Abs(a) + b;
}

We crafted our own Abs function which is a constexpr, and the body of Abs also doesn’t break any rule. Also, at the call site (inside Sum), the expression evaluates to a constexpr. Hence, the call to Sum(-10, 20) will be a compile-time constant expression resulting to 30.

Static if statement

The if constexpr statement can be used to conditionally compile code. The condition must be a constant expression. The branch not selected is discarded. A discarded statement inside a template is not instantiated. For example:

template<class T, class ... Rest>
void g(T &&p, Rest &&...rs)
{
  // ... handle p
  if constexpr (sizeof...(rs) > 0)
    g(rs...);  // never instantiated with an empty argument list
}

In addition, variables and functions that are odr-used only inside discarded statements are not required to be defined, and discarded return statements are not used for function return type deduction.

if constexpr is distinct from #ifdef. #ifdef conditionally compiles code, but only based on conditions that can be evaluated at preprocessing time. For example, #ifdef could not be used to conditionally compile code depending on the value of a template parameter. On the other hand, if constexpr cannot be used to discard syntactically invalid code, while #ifdef can.

if constexpr(false) {
    foobar;  // error; foobar has not been declared
    std::vector<int> v("hello, world");  // error; no matching constructor
}

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow