Search code examples
c++language-lawyerc++20constexprconsteval

C++20 consteval functions and constexpr variables - are they guaranteed to be evaluated at compilation time?


In C++20, we have the consteval keyword that declares an immediate function. For example:

consteval int f(int x) { return x * x; }

Such a function is required to produce a constant expression. However, according to the standard, a constant expression is not required to be actually evaluated at compilation time, unless it is used in a place where a constant is required, such as in a template parameter. For example, nothing in the standard seems to require that this is evaluated at compilation time:

int a = f(10);

However, the requirements strongly suggest that an immediate function is meant to be evaluated at compilation time.

It also appears that nothing in the standard requires that a constexpr variable be evaluated at compilation time. So even if we make the variable constexpr, i.e.

constexpr int a = f(10);

it only asserts that f(10) is a constant expression and makes a a constant expression (but again nothing about constant expressions require that they are actually evaluated at compilation time). However, just like before, the requirements for constexpr variables strongly suggest that they are meant to be evaluated at compilation time.

Only constinit variables are defined differently - constinit variables are required to have static initialization, so they must be calculated at compilation time and embedded directly in the binary. However, this answer to a related question says that constexpr implies constinit, at least for global variables, which seems to contradict what I've written above.

So, are consteval functions and constexpr variables guaranteed to be evaluated at compilation time?


Side note: My actual use case involves trying to initialize a field in a struct with a constant, like this:

consteval int my_complicated_calculation() {
    // do complicated mathematics and return an int
}
struct A {
    int value;
    A() : value{my_complicated_calculation()} {}
}

Solution

  • The standard does not have the concept of "compile time". There is only "constant evaluation" and a "constant expression". Implementations may implement "constant expressions" such that they are evaluated at compile time.

    But there is no C++ standard-defined behavior you can point to in the actual C++ program that makes a "constant expression" equivalent to compile-time compilation. It is 100% legal for a compiler to emit code for constant expressions, and it always has been.

    From [expr.const]/13:

    "An immediate invocation [ie: calling a consteval function] shall be a constant expression

    So calling a consteval function is a constant expression. Does this mean that the compiler certainly will not emit actual assembly for this function that gets called at runtime? No; that's a matter of the quality of the implementation.

    However, certain uses of a constant expression do inform various compiler decisions. This means that, while the compiler does not in general have to evaluate them at compile time, if the value is used in a place that affects the basic act of compilation, the compiler must in that instance evaluate the expression right then.

    For example:

    std::is_same_v<array<int, some_constexpr_func(3, 4)>, array<int, 7>>
    

    Whether this (constant) expression evaluates to true or false depends on what some_constexpr_func(3, 4) returns. If it returns the sum of its parameters, it is true. And the compiler needs to know that, because it needs to emit the code for the std::array<int, S> type, which as part of its type includes its template parameters. So it needs to know what they are.

    Outside of cases where constant evaluation affects compiler decisions, whether any constant evaluation happens at compile time is up to the quality of your compiler.