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.