I seem can't understand why the following code with type const int compiles:
int main()
{
using T = int;
const T x = 1;
auto lam = [] (T p) { return x+p; };
}
$ clang++ -c lambda1.cpp -std=c++11
$
while this one with type const double doesn't:
int main()
{
using T = double;
const T x = 1.0;
auto lam = [] (T p) { return x+p; };
}
$ clang++ -c lambda2.cpp -std=c++11
lambda1.cpp:5:32: error: variable 'x' cannot be implicitly captured in a lambda with no capture-default specified
auto lam = [] (T p) { return x+p; };
^
lambda1.cpp:4:11: note: 'x' declared here
const T x = 1.0;
^
lambda1.cpp:5:14: note: lambda expression begins here
auto lam = [] (T p) { return x+p; };
^
1 error generated.
yet compiles with constexpr double:
int main()
{
using T = double;
constexpr T x = 1.0;
auto lam = [] (T p) { return x+p; };
}
$ clang++ -c lambda3.cpp -std=c++11
$
Why behaviour for int differs from double, or for any other type than int, i.e. int is accepted with const qualifier, yet double/other types must be constexpr? Also, why this code compiles with C++11, my understanding from [1] is that such implicit captures is C++14 feature.
.. [1] how is this lambda with an empty capture list able to refer to reaching-scope name?
The reason for this ends up being to maintain C++03 compatibility since in C++03 const integral or const enumeration types initialized with a constant expression were usable in a constant expression but this was not the case for floating point.
The rationale for keeping the restriction can be found in defect report 1826 which came after C++11(this explains the ABI break comment) and asks (emphasis mine):
and the response was:
We can note that the question points out that allowing const floating point variables to be constant expression would be an ABI-break with respect to lambda capture.
This is the case since a lambda does not need to capture a variable if it is not odr-used and allowing const floating point variables to be constant expressions would allow them to fall under this exception.
This is because an lvalue-to-rvalue conversion of a const integer or enumeration type initialized with a constant expression or a constexpr literal type is allowed in a constant expression. No such exception exists for const floating point types initialized with a constant expression. This is covered in the draft C++11 standard section [expr.const]p2:
and includes in [expr.const]p2.9
Changing this would potentially effect the size of a lambda object if they no longer had to capture non-odr-used const floating point values which is an ABI break. This restriction was originally put in place to keep C++03 compatibility and to encourage the use of constexpr but now this restriction is in place it becomes hard to remove it.
Note, in C++03 we were only allowed to specify an in class constant-initializer for const integral or const enumeration types. In C++11 this was expanded and we were allowed to specify constant-initializer for constexpr literal types using a brace-or-equal-initializer.
According to the standard §5.1.2/p12 Lambda expressions [expr.prim.lambda] (Emphasis Mine):
What the standard states here is that a variable in a lambda needs to be captured if it is odr-used. By odr-used the standard means that the variable definition is needed, either because its address is taken or there's a reference to it.
This rule however has exceptions. One of them that is of particular interest is found in the standard §3.2/p3 One definition rule [basic.def.odr] (Emphasis Mine):
Now if in the examples:
and
apply an lvalue to rvalue conversion on
x
we get a constant expression since in the first examplex
is an integral constant and in the second examplex
is declaredconstexpr
. Therefore,x
doesn't need to be captured in these contexts.However, this is not the case for the example:
in this example if we apply lvalue to rvalue conversion to
x
we don't get a constant expression.Now you might be wondering why is this the case since
x
isconst double
. Well the answer is that a variable declared without aconstexpr
qualifies as a constant expression if either is a constant integral or an enumeration type, and is initialized at declaration time with a constant expression. This is justified by the standard in §5.20/p2.7.1 Constant expressions [expr.const] (Emphasis Mine):Thus,
const double
variables need to be captured since an lvalue-to-rvalue conversion don't yell a constant expression. Therefore rightfully you get a compiler error.