Search code examples
c++language-lawyerconstexprcompile-time-constantconstant-expression

constexpr general confusion


I have made a thread yesterday but I think that it was unclear and the replies I got didn't solve my confusion at all. So, I'll try to make the example simpler.

Why is this allowed:

constexpr int incr(int k1)
{
    return k1 + 5;
}

constexpr int foo(int k)  // runs in compile time
{
    return incr(k);
}

int main() {
    constexpr int x = 5;
    constexpr int y = foo(4);
}

... But this very similar function is not?

constexpr int incr(int k1)
{
    return k1 + 5;
}

constexpr int foo(int k)  // runs in compile time
{
    constexpr int x = incr(k); // k: not a constant expression
    return x;
}

int main() {
    constexpr int x = 5;
    constexpr int y = foo(4);
}

How exactly are these two functions different in the context of constant evaluation? To me they look almost exactly the same, except one returns instantly while the other first computes the value and assigns it to the constexpr variable.

If you can, please also refer to the standard so that I can read further!


Solution

  • The rule about constexpr variables is [dcl.constexpr]/6,

    [...] In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression ([expr.const]). A constexpr variable that is an object, as well as any temporary to which a constexpr reference is bound, shall have constant destruction.

    Every constant expression must be a core constant expression, with some additional restrictions that are not relevant here, so I won't get into them ([expr.const]/13). If the initialization of a constexpr variable is not a core constant expression, the program is ill-formed.

    The particular rule violated by the second example is [expr.const]/5.9.

    An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

    • [...]
    • an lvalue-to-rvalue conversion unless it is applied to
      • a non-volatile glvalue that refers to an object that is usable in constant expressions, or
      • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;

    In the declaration

    constexpr int x = incr(k);
    

    the full-expression "initialize an int variable from incr(k)" fails to be a core constant expression because, when it is evaluated, it needs to perform an lvalue-to-rvalue conversion on k in order to get the value to initialize k1 with. The variable k is not usable in constant expressions, nor did its lifetime begin within the full-expression "initialize an int variable from incr(k)".

    In the first example, which your compiler accepts, incr(k) is also being evaluated at compile time, but it is not required to be a core constant expression, so there is no problem. It's very confusing at first, but we need to remember that something that is not a core constant expression can be evaluated as part of a "bigger" evaluation that is a core constant expression. In this case, it is the enclosing constexpr variable initialization (that of y) that is required to be a core constant expression—and it is, because it creates the function parameter k (initializing it with the value 4) and then reads from it. To put it another way, if E is "initialize y with foo(4)", then the lifetime of k begins within E, so the read of k can occur without preventing E from being a core constant expression.