Reputation: 311
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!
Upvotes: 4
Views: 611
Reputation: 8998
constexpr
modifier, when applied to functions, merely gives a possibility of the function to be run at compile time (moreover, in the C++23 draft a constexpr
function doesn't get the former restrictions anymore, thus you better read it as a contract, "promise" given by the author to support constexpr
semantic). The modifier doesn't cancel the fact, that the same function might be run at run-time as well:
int main() {
constexpr int compileVar = foo(2);
int param = 4;
int runtimeVar = foo(param);
}
Thus there is no guarantee that the arguments passed to a constexpr
function evaluated at compile time.
On the other hand, constexpr
modifier, when applied to variables, can only be evaluated at compile time, and the compiler reasonably prevents you from using a (possibly) runtime parameter of the function to evaluate a compile-time variable.
How exactly are these two functions different in the context of constant evaluation?
I think the main misconception here is that all parts of a constexpr
function are evaluated at compile time if the function is evaluated at compile time, no matter if the variables inside have constexpr
modifier or not. So the proper equivalent of this function:
constexpr int foo(int k)
{
return incr(k);
}
would be:
constexpr int foo(int k)
{
int x = incr(k);
return x;
}
Upvotes: 1
Reputation: 119457
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]). Aconstexpr
variable that is an object, as well as any temporary to which aconstexpr
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.
Upvotes: 2