comma operator makes lambda expression non-constexpr - c++

According to [this Q&A] since c++11 comma operator is constexpr capable. According to [this Q&A] constexpr variable should not be captured by lambda but should be usable inside its body.
Both these rules make following code compilable in clang:
//Example 1
template <int>
struct Foo {};
int main() {
constexpr int c = 1;
static_cast<void>(Foo<(c, 2)>{});
}
//Example 2
template <int>
struct Foo {};
int main() {
constexpr int c = 1;
auto lambda = []{return c * 2;};
static_cast<void>(Foo<lambda()>{});
}
However while both these examples compile successfully on clang (that declares constexpr lambda support that is -- 8.0.0) the following snippet doesn't and I can't imagine why... Any ideas?
template <int>
struct Foo {};
int main() {
constexpr int c = 1;
auto lambda = []{return (c, 2);};
static_cast<void>(Foo<lambda()>{});
}
Compilation error:
variable 'c' cannot be implicitly captured in a lambda with no capture-default specified
[live demo]

Its seems to be a clang bug, according to [basic.def.odr]/4:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.20) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (7.1) is applied to e, or e is a discarded-value expression.
As has been commented, the issue is not limited to the comma operator but for every discarded expressions, these expression doesn't constitute odr-use, hence it must be accepted.

This is a clang bug, if we look at a simpler case:
constexpr int c = 1;
auto lambda = [] {return c,2;};
clang also considers this ill-formed (see it live), the lambda is required to capture an automatic variable if it odr-uses it see expr.prim.lambda.capturep8:
An entity is captured if it is captured explicitly or implicitly. An entity captured by a lambda-expression is odr-used in the scope containing the lambda-expression. If *this is captured by a local lambda expression, its nearest enclosing function shall be a non-static member function. If a lambda-expression or an instantiation of the function call operator template of a generic lambda odr-uses this or a variable with automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression. If a lambda-expression captures an entity and that entity is not defined or captured in the immediately enclosing lambda expression or function, the program is ill-formed. ...
and discarded value expression is not an odr-use.
I found a similar bug report [rejects valid] constexpr non-scalar variable not usable in lambda without capture or local class.

Related

Must constexpr expressions be captured by a lambda in C++?

Here is a piece of code that won't compile in MSVC 2015 (ignore the uninitialized value access):
#include <array>
int main() {
constexpr int x = 5;
auto func = []() {
std::array<int, x> arr;
return arr[0];
};
func();
}
It complains that:
'x' cannot be implicitly captured because no default capture mode has been specified
But x is a constexpr! x is known at compile time to be 5. Why does MSVC kick up a fuss about this? (Is it yet another MSVC bug?) GCC will happily compile it.
The code is well-formed. The rule from [expr.prim.lambda] is:
If a lambda-expression or an instantiation of the function call operator template of a generic lambda odr-uses (3.2) this or a variable with
automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression.
Any variable that is odr-used must be captured. Is x odr-used in the lambda-expression? No, it is not. The rule from [basic.def.odr] is:
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 non-trivial
functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).
x is only used in a context where we apply the lvalue-to-rvalue conversion and end up with a constant expression, so it is not odr-used, so we do not need to capture it. The program is fine. This is the same idea as why this example from the standard is well-formed:
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
};
// ...
}
Even though x is a constexpr, it is no different from any other object, otherwise, and follows the same rules with regards to scoping. There are no exceptions to scoping rules for constexprs, and a lambda must be coded to explicitly capture it.

Using of not captured variable in lambda

I can not quite understand an example from C++14 standard draft N4140 5.1.2.12 [expr.prim.lambda].
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:
odr-uses the entity, or
names the entity in a potentially-evaluated expression 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 ]
I thought that the beginning of a phrase a lambda-expression with an associated capture-default should prohibit any implicit capture (and it's confirmed by comment), therefore #1 call will lead to an error (something about using not captured variable). So how it works? What will be first argument of f? What if g will be called after exiting test() scope? What if I change #1 signature to void(const int&)?
--
upd: Thanks to all for explanation of how it works. Later I'll try to find and post references to standard about this case.
As T.C. said in his comment, #1 does not require a capture as x is known at compile time and is therefore baked into the lambda. Not unlike how the function f is known at compile time so it doesn't need to be captured.
I believe if you change f's signature to int const & you are now attempting to pass the address of the constant which is on the stack, thus subject to changes, and it would require capturing x by value or reference.

Why type const double is not captured by lambda from reaching-scope, but const int is?

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):
A const integer initialized with a constant can be used in constant expressions, but a const floating point variable initialized with a constant cannot. This was intentional, to be compatible with C++03 while encouraging the consistent use of constexpr. Some people have found this distinction to be surprising, however.
It was also observed that allowing const floating point variables as constant expressions would be an ABI-breaking change, since it would affect lambda capture.
One possibility might be to deprecate the use of const integral variables in constant expressions.
and the response was:
CWG felt that the current rules should not be changed and that programmers desiring floating point values to participate in constant expressions should use constexpr instead of const.
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:
A conditional-expression is a core constant expression unless it involves one of the following as a potentially
evaluated subexpression [...]
and includes in [expr.const]p2.9
an lvalue-to-rvalue conversion (4.1) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized
with a constant expression, or
a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an
object, or
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):
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.

how is this lambda with an empty capture list able to refer to reaching-scope name?

In the C++14 standard § 5.1.2/12 it shows an example of a lambda expression that apparently seems to be able to refer to a reaching scope's variable x, even though:
the capture list is empty, i.e. no capture-default
the comment says that it "does not capture x"
Here's the example:
void f(int, const int (&)[2] = {}) { } // #1
void test() {
const int x = 17;
auto g = [](auto a) {
f(x); // OK: calls #1, does not capture x
};
}
See that it does compile. It seems to hinge on x being const; if the const is removed, it no longer compiles for the reasons one would expect (capture list is empty). It happens even if I make the parameter be int so that it's no longer a generic lambda.
How is it possible for the lambda to refer to x even though the capture list is empty? And how is this possible while at the same time apparently not capturing x (as the comment says)?
The closest thing I found on this subject was someone else tangentially noticing this in a comment.
Here's the full section 5.1.2/12 from the standard:
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 init-capture’s associated non-static data member), is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:
odr-uses (3.2) the entity, or
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 ]
You have the right quote. A variable needs to be captured if it is odr-used. ODR-use means basically that the variable is used in a context where it needs a definition. So either its address is taken, or a reference is taken to it, etc. One key exception is, from [basic.def.odr]:
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, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause
5).
So in your example, applying lvalue-to-rvalue conversion on x yields a constant expression (since x is a constant integral), so it's not odr-used. Since it's not odr-used, it doesn't have to be captured.
On the other hand, if x were bound to a reference (e.g. f took its argument as const int&), then it would be odr-used, and so would have to be captured. In the second example presented, x's "odr-use-ness" is dependent on what the generic lambda argument is, so that is considered captured anyway for sanity's sake.

Can I use a constexpr value in a lambda without capturing it?

I would want to use a constexpr value in a lambda. Reading the answer to
Using lambda captured constexpr value as an array dimension, I assumed the following should work:
#include<array>
int main()
{
constexpr int i = 0;
auto f = []{
std::array<int, i> a;
};
return 0;
}
However, Clang 3.8 (with std=c++14) complains that
variable 'i' cannot be implicitly captured in a lambda with no
capture-default specified
Should this be considered a bug in clang 3.8?
BTW:
The above code does compile with gcc 4.9.2.
If I change the lambda expresion to capture explicitly:
...
auto f = [i]{
...
clang 3.8 compiles it, but gcc 4.9.2 fails:
error: the value of ‘i’ is not usable in a constant expression
...
Should this be considered a bug in clang 3.8?
Yep. A capture is only needed if [expr.prim.lambda]/12 mandates so:
Note in particular the highlighted example. f(x) does not necessitate x to be captured, because it isn't odr-used (overload resolution selects the overload with the object parameter). The same argumentation applies to your code - [basic.def.odr]/3:
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 non-trivial functions…
This requirement is certainly met.
…and, if x is an object, ex is an element of
the set of potential results of an expression e, where either the
lvalue-to-rvalue conversion (4.1) is applied to e, or e is a
discarded-value expression (Clause 5).
i is its set of potential results as per [basic.def.odr]/(2.1), and the l-t-r conversion is indeed immediately applied as its passed to a non-type template parameter of object type.
Hence, as we have shown that (12.1) isn't applicable - and (12.2) clearly isn't, either - Clang is wrong in rejecting your snippet.