Consider this example which declares a variable as constexpr, captures it by copy in a lambda, and declares another constexpr variable which is the result of a constexpr function unwrapping a non-type template parameter from the original variable.
#include <utility>
template<int I>
constexpr auto unwrap(std::integral_constant<int, I>) {
return I;
}
int main() {
constexpr auto i = std::integral_constant<int, 42>{};
constexpr auto l = [i]() {
constexpr int x = unwrap(i);
};
}
Clang (trunk) accepts this code. (wandbox)
GCC (trunk) fails with the following error message (wandbox):
lambda_capture.cpp:11:31: error: the value of ‘i’ is not usable in a constant expression
constexpr int x = unwrap(i);
^
lambda_capture.cpp:10:28: note: ‘i’ was not declared ‘constexpr’
constexpr auto l = [i]() {
Which compiler is correct? It seems to me that this is a GCC bug, where the constexpr-ness of lambda captures is not correctly propagated to the lambda context.
Both implementations are bugged, but I'm inclined to think that GCC got the right answer here.
Dropping the capture of
i
causes Clang to refuse to compile the code. That means it clearly has a bug somewhere.[expr.const]/2.12:
Clang's behavior is schizophrenic: if the use of
i
in the body is not an odr-use, then it doesn't need to be captured, yet it rejects the code in the OP if the explicit capture is removed; OTOH, if it is an odr-use, then by the aboveunwrap(i)
isn't a constant expression, and so it should reject the initialization ofx
.GCC's lambda implementation is woefully bad with respect to odr-use. It does constant-folding ultra-early, resulting in all kinds of subtle mischief. On the other hand, for explicit captures it transforms all uses, whether or not it's actually an odr-use. The aggressive constant folding means that it accepts OP's code if the capture of
i
is removed.Assuming that
unwrap(i)
does odr-usei
, then it is correct that, per [expr.const]/2.12, OP's code is ill-formed.Does
unwrap(i)
actually odr-usei
? That question boils down to whether copy-initializing the parameter object ofunwrap
counts as applying an lvalue-to-rvalue conversion toi
. I don't see anything in the standard that explicitly says that an lvalue-to-rvalue conversion is applied here, and instead [dcl.init]/17.6.2 indicates that we call a constructor (in this case, the trivial implicitly defined copy constructor) passingi
as the argument bound to its parameter, and reference binding is a classic example of odr-use.To be sure, applying an l-to-r conversion would result in a copy-initialization of an
integral_constant<int, 42>
object fromi
, but the problem here is that nothing in the standard says the converse - that all copy-initializations of anintegral_constant<int, 42>
object fromi
count as l-to-r conversions.