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.
Visual Studio 2017 version 15.3 and later (available in /std:c++17 mode and later): A lambda expression may be declared as constexpr or used in a constant expression when the initialization of each data member that it captures or introduces is allowed within a constant expression.
A constexpr (which is short for “constant expression”) variable can only be a compile-time constant. If the initialization value of a constexpr variable is not a constant expression, the compiler will error.
constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.
In C++11, constexpr member functions are implicitly const.
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:
An expression
e
is a core constant expression unless the evaluation ofe
, following the rules of the abstract machine, would evaluate one of the following expressions:
- [...]
- in a lambda-expression, a reference to [...] a variable with automatic storage duration defined outside that lambda-expression, where the reference would be an odr-use;
- [...]
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 above unwrap(i)
isn't a constant expression, and so it should reject the initialization of x
.
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-use i
, then it is correct that, per [expr.const]/2.12, OP's code is ill-formed.
Does unwrap(i)
actually odr-use i
? That question boils down to whether copy-initializing the parameter object of unwrap
counts as applying an lvalue-to-rvalue conversion to i
. 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) passing i
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 from i
, but the problem here is that nothing in the standard says the converse - that all copy-initializations of an integral_constant<int, 42>
object from i
count as l-to-r conversions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With