Why isn't type const double written by lambda from scope, but const int is?

I don't seem to 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 does not matter:

 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. 

still compiled 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 is the behavior for int different from double or for any type other than int, i.e. int is accepted with the const qualifier, but should double / other types be constexpr? Besides why this code compiles with C ++ 11, my understanding from [1] is that such implicit captures are a function of C ++ 14.

.. [1] How can this lambda with an empty capture list refer to the scope name?

+6
source share
2 answers

The reason for this is to maintain compatibility with C ++ 03, since C ++ 03 lists const integer or const types that are initialized with a constant expression, where they are used in a constant expression, but this does not apply to floating point.

The rationale for maintaining the restriction can be found in the defect report 1826 , which appeared after C ++ 11 (this explains the comment about disabling ABI) and asks (my attention):

A conjugated integer initialized by a constant can be used in constant expressions, but a floating-point variable const, initialized by a constant, cannot. This one was intentional to be compatible with C ++ 03, encouraging consistent use of constexpr . However, some people find this difference surprising.

It has also been observed that allowing const variables with floating point as constant expressions is a change in ABI because it affects lambda capture.

One of the possibilities may be the rejection of the use of constant integral variables in constant expressions.

and the answer was:

CWG believed that the current rules should not change and that programmers who want to use floating point values ​​to participate in constant expressions should use constexpr instead of const.

You may notice that the question indicates that allowing constant expression of floating point constants is an ABI break with respect to lambda capture.

This is because lambda does not need to capture a variable if it is not used by odr and that constant variables of a constant constant are constant expressions, allowing them to fall under this exception.

This is because a lvalue-to-rval conversion of type const or an enumeration type initialized by a constant expression or type constexpr literal is allowed in a constant expression. Such an exception does not exist for const floating point types initialized with a constant expression. This is described in the standard section of the C ++ 11 5.19 project:

A conditional expression is an expression of a basic constant if it is not associated with one of the following as a potentially evaluated subexpression [...]

and includes:

  • lvalue-to-rvalue conversion (4.1) if it does not apply to
    • an integral or enumeration type value that refers to a non-volatile constant object with previous initialization is initialized with a constant expression or
    • a literal type glvalue that refers to a non-volatile object defined using constexpr, or that refers to a sub-object of such an object, or

Changing this could potentially affect the size of the lambda object if they no longer needed to write the non-odr-used floating point constant values, which are ABI breaks. This restriction was originally introduced to maintain compatibility with C ++ 03 and encourage the use of constexpr, but now this restriction in place is becoming difficult to remove.

Note that in C ++ 03 we were allowed to specify an in-class constant initializer for constant continuous or continuous enumeration types. In C ++ 11, this was expanded, and we were allowed to specify a constant initializer for constexpr literal types using an element for alignment or alignment.

+2
source

According to the standard Β§5.1.2 / p12 Lambda expressions [expr.prim.lambda] ( Emphasis Mine ):

A lambda expression with an associated default capture that does not explicitly commit this or a variable with automatic storage duration (this excludes any identification expression that has been found to refer to the initcaptures associated non-static data element) is called to implicitly capture an object (i.e. . this or variable) if the compound statement:

(12.1) - odr-uses (3.2) an object or

(12.2) - names the object in the potentially evaluated expression (3.2), where the inclusion of the full expression depends on the general parameter of the lambda declared within the scope of the lambda expression [Example:

 void f(int, const int (&)[2] = {}) { } // #1 void f(const int&, const int (&)[1]) { } // #2 void test() { const int x = 17; auto g = [](auto a) { f(x); // OK: calls #1, does not capture x }; auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2]{}; f(x, selector); // OK: is a dependent expression, so captures x }; } 

- end of example] All such implicitly fixed objects must be declared within the scope of the lambda expression. [Note: Implicit capture of an object by a nested lambda expression can cause it to be implicitly captured using the containing lambda expression (see below). Implicit use of odr of this can lead to implicit capture. - end note]

What the standard state means here, so the variable in lambda must be captured if it is used by odr. When using odr, the standard one is used, meaning that the definition of the variable is necessary, either because its address is busy, or there is a link to it.

However, this rule has exceptions. One of them is of particular interest in the standard Β§3.2 / p3 One definition rule [basic.def.odr] ( Emphasis Mine ):

The variable x, whose name is displayed as a potentially computed expression ex - odr - is used by ex, if the lvalue-to-rvalue (4.1) to x conversion is not applied, it gives a constant expression (5.20) that does not call any non-trivial functions and, if x is an object , ex is an element of the set of potential results of the expression e, ...

Now, if in the examples:

 int main() { using T = int; const T x = 1; auto lam = [] (T p) { return x+p; }; } 

and

 int main() { using T = double; constexpr T x = 1.0; auto lam = [] (T p) { return x+p; }; } 

applying the conversion of lvalue to rvalue by x , we get a constant expression, because in the first example x is an integral constant, and in the second example x declared constexpr . Therefore, x does not need to be captured in these contexts.

However, this does not apply to the example:

 int main() { using T = double; const T x = 1.0; auto lam = [] (T p) { return x+p; }; } 

in this example, if we apply the conversion of lvalue to rvalue to x , we do not get a constant expression.

Now you may be wondering why this is so, since x is a const double . Well, the answer is that a variable declared without constexpr qualifies as a constant expression if it is either a constant integral or an enumeration type, and is initialized during the declaration with a constant expression. This is justified by the standard in Β§5.20 / p2.7.1. Constant Expressions [expr.const] ( Emphasis Mine ):

The conditional expression e is an expression of a constant constant, the estimate e, following the rules of the abstract machine (1.9), will evaluate one of the following expressions:

...

(2.7) - the lvalue-to-rvalue transformation (4.1), if it does not apply to

(2.7.1) is a non-volatile glvalue of an integral or enumerated type, which refers to a complete non-volatile const object with the preceding initialization, initialized with a constant expression , ...

Thus, const double variables must be captured since the lvalue-to-rval conversion does not invoke a constant expression. Therefore, by right you get a compiler error.

+6
source

All Articles