Here is a similar, but simpler version of the code that doesn't compile:
constexpr auto func(int a) {
constexpr auto b = a;
return b;
};
int main() {
constexpr int x = 1;
constexpr int y = func(x);
}
Here, because x is constexpr, it's ok to read the value of x within the initialization of the constexpr variable y.
However, that's not good enough, because not only does y need to have a constant expression as its initializer, but so does b, which is also declared constexpr. And this is where the issue arises. If b had not been declared constexpr, then the code would be fine.
In order for constexpr auto b = ...; to be valid, the compiler has to be able to evaluate it as a constant expression in the context where it appears—not the larger enclosing context in which func is called. In the context where the initialization of b appears, the value of a is not known until runtime. func may be called in both constant expressions and non-constant expressions. So the check that b is a constant expression fails.
In the OP's program, it's very similar. lambda2, which captures i, is of a closure type having a data member of type const int ([expr.prim.lambda.capture]/10). When func is called, the statement
constexpr auto k = c;
in its body is instantiated, with c being a function parameter of that closure type. At this point, the initialization of k has to be checked as a constant expression in the context where it appears, not the context of the enclosing constant evaluation. The initialization uses the copy constructor of the closure type, which is defaulted ([expr.prim.lambda.closure]/15) and thus performs a memberwise-copy. It must therefore read the const int member of the function parameter c.
To give a bit more detail:
[dcl.constexpr]/6 states that "In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression". Let's apply this to the declaration of k. In order for it to be a constant expression, it has to be a core constant expression that satisfies some other conditions ([expr.const]/14). [expr.const]/5 gives the conditions for an expression E to be a core constant expression. In this case E is the init-declarator of k.
When k is initialized, the const int data member of c will be read, as discussed above. This is an lvalue-to-rvalue conversion. [expr.const]/5.9 states that an lvalue-to-rvalue conversion is not allowed in a core constant expression E except when applied to either
- 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
The second bullet is clearly not satisfied: the lifetime of the const int member of c does not start within the evaluation of E (i.e. the init-declarator of k); it starts beforehand, when the function is called. As for the first bullet, a function parameter is not usable in constant expressions because it can never be both "constant-initialized" and "potentially-constant" ([expr.const]/4). In particular, a function parameter of scalar or reference type cannot satisfy [expr.const]/2.1 and a function parameter of class type cannot satisfy [expr.const]/3.
(By the way, even in a consteval function, a function parameter's value still isn't a constant expression, despite the fact that a consteval function can only be called during constant evaluation. There is no fundamental reason why it can't be supported in the consteval case, but if it were allowed then it would create implementation difficulties and turn almost every consteval function into an implicit template.)
constexpr auto k = c;will fail to be a proper constexpr. If you just remove theconstexprso that the body of the template function is under the function constexpr instead of having another constexpr scope (that'll fail to be constexpr), you should be fine. I leave it to a language-lawyer can cite chapter-and-verse from the standard, and use the standard-ese terminology.constexpr) copy the lambda, or call itconstexpr auto k = c();?constexpr, sok = cwould use nonconstexprc. with capture, the member has to be copied (but it is notconstexpr), whereas captureless lambda won't usethisat all, and is still viable inconstexpr.clike inconstexpr auto k= c();while gcc and msvc accepts it. See demo I'll ask a separate question for that.