According to the standard §5.1.2/p12 Lambda expressions [expr.prim.lambda] (Emphasis Mine):
A lambda-expression with an associated capture-default that does not
explicitly capture this
or a variable with automatic storage duration
(this excludes any id-expression that has been found to refer to an
initcapture’s associated non-static data member), is said to
implicitly capture the entity (i.e., this
or a variable) if the
compound-statement:
(12.1) - odr-uses (3.2) the entity, or
(12.2) - names the entity in a potentially-evaluated expression (3.2) where the
enclosing full-expression depends on a generic lambda parameter
declared within the reaching 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 example ] All such implicitly captured entities shall be
declared within the reaching scope of the lambda expression. [ Note:
The implicit capture of an entity by a nested lambda-expression can
cause its implicit capture by the containing lambda-expression (see
below). Implicit odr-uses of this can result in implicit capture. —
end note ]
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):
A variable x whose name appears as a potentially-evaluated expression
ex is odr-used by ex unless applying the lvalue-to-rvalue conversion
(4.1) to x yields a constant expression (5.20) that does not invoke
any nontrivial functions and, if x is an object, ex is an element of
the set of potential results of an 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; };
}
apply an lvalue to rvalue conversion on x
we get a constant expression since in the first example x
is an integral constant and in the second example x
is declared constexpr
. Therefore, x
doesn't need to be captured in these contexts.
However, this is not the case for 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 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
is const double
. Well the answer is that a variable declared without a constexpr
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):
A conditional-expression e is a core constant expression unless the
evaluation of e, following the rules of the abstract machine (1.9),
would evaluate one of the following expressions:
...
(2.7) - an lvalue-to-rvalue conversion (4.1) unless it is applied to
(2.7.1) - a non-volatile glvalue of integral or enumeration type that
refers to a complete non-volatile const object with a preceding
initialization, initialized with a constant expression, ...
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.