I wrote the following code that has two lambdas. One of them explicitly captures i while the other doesn't. Note i is constexpr so we don't need to capture it explicitly.
My question is why func(lambda2) doesn't compile while func(lambda) does? Note lambda doesn't explicitly capture i while lambda2 has i in its capture list. Demo
template <typename T>
constexpr auto func(T c) {
constexpr auto k = c;
return k;
};
int main() {
constexpr int i = 0;
constexpr auto lambda = []()constexpr { constexpr int j = i; return j; }; //compiles
constexpr auto lambda2 = [i]()constexpr { constexpr int j = i; return j; };//compiles
constexpr auto a = func(lambda); //compiles as expected
constexpr auto b = func(lambda2); //this doesn't compile why?
}
Clang says:
error: constexpr variable 'k' must be initialized by a constant expression
3 | constexpr auto k = c;
| ^ ~
/home/insights/insights.cpp:18:24: note: in instantiation of function template specialization 'func<(lambda at /home/insights/insights.cpp:13:30)>' requested here
18 | constexpr auto b = func(lambda2); //this doesn't compile why?
| ^
/home/insights/insights.cpp:3:24: note: function parameter 'c' with unknown value cannot be used in a constant expression
3 | constexpr auto k = c;
| ^
/home/insights/insights.cpp:3:24: note: in call to '(lambda at /home/insights/insights.cpp:13:30)(c)'
/home/insights/insights.cpp:2:23: note: declared here
2 | constexpr auto func(T c) {
| ^
Here is a similar, but simpler version of the code that doesn't compile:
Here, because
xisconstexpr, it's ok to read the value ofxwithin the initialization of theconstexprvariabley.However, that's not good enough, because not only does
yneed to have a constant expression as its initializer, but so doesb, which is also declaredconstexpr. And this is where the issue arises. Ifbhad not been declaredconstexpr, 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 whichfuncis called. In the context where the initialization ofbappears, the value ofais not known until runtime.funcmay be called in both constant expressions and non-constant expressions. So the check thatbis a constant expression fails.In the OP's program, it's very similar.
lambda2, which capturesi, is of a closure type having a data member of typeconst int([expr.prim.lambda.capture]/10). Whenfuncis called, the statementin its body is instantiated, with
cbeing a function parameter of that closure type. At this point, the initialization ofkhas 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 theconst intmember of the function parameterc.To give a bit more detail:
[dcl.constexpr]/6 states that "In any
constexprvariable declaration, the full-expression of the initialization shall be a constant expression". Let's apply this to the declaration ofk. 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 ofk.When
kis initialized, theconst intdata member ofcwill 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 eitherThe second bullet is clearly not satisfied: the lifetime of the
const intmember ofcdoes not start within the evaluation of E (i.e. the init-declarator ofk); 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
constevalfunction, a function parameter's value still isn't a constant expression, despite the fact that aconstevalfunction can only be called during constant evaluation. There is no fundamental reason why it can't be supported in theconstevalcase, but if it were allowed then it would create implementation difficulties and turn almost everyconstevalfunction into an implicit template.)