Must constexpr expressions be captured by a lambda in C++? - 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.

Related

comma operator makes lambda expression non-constexpr

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.

Generic lambda and its argument as constant expression

The following code is accepted by GCC 7.2 and clang 5.0.0, but is rejected by Microsoft VS 2017 15.5.0 Preview 5 and Intel C++ compiler 19:
struct S { };
constexpr int f(S)
{
return 0;
}
int main()
{
auto lambda = [](auto x)
{
constexpr int e = f(x);
};
lambda(S{});
}
Microsoft:
<source>(12): error C2131: expression did not evaluate to a constant
Intel:
<source>(12): error: expression must have a constant value
constexpr int e = f(x);
^
<source>(12): note: the value of parameter "x" (declared at line 10) cannot be used as a constant
constexpr int e = f(x);
^
If I replace f(x) with f(decltype(x){}), both Microsoft and Intel do not complain. I understand that x is not a constant expression, but it is not used inside f. This is probably why GCC and clang do not complain.
I guess that Microsoft and Intel compilers are correct in rejecting this code. What do you think?
From [expr.const]:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
[...]
an lvalue-to-rvalue conversion unless it is applied to
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, or
a non-volatile glvalue that refers to a subobject of a string literal, or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
[...]
In f(x), we do an lvalue-to-rvalue conversion on x. x isn't of integral or enumeration type, it's not a subobject of a string-literal, it's not an object defined with constexpr, and its lifetime did not begin with the evaluation of f(x).
That seems to make this not a core constant expression.
However, as Casey points out, since S is empty, nothing in its implicitly-generated copy constructor would actually trigger this lvalue-to-rvalue conversion. That would mean that nothing in this expression actually violates any of the core constant expression restrictions, and hence gcc and clang are correct in accepting it. This interpretation seems correct to me. constexpr is fun.
This is not a gcc/clang bug. The same behavior can be reproduced in C++11 with a template function:
template <typename T>
void foo(T x)
{
constexpr int e = f(x);
}
int main()
{
foo(S{});
}
on godbolt.org
The question is, given...
template <typename T>
void foo(T x)
{
constexpr int e = f(x);
}
...is f(x) a constant expression?
From [expr.const]:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor
S{} and 0 are constant expressions because it doesn't violate any of the rules in [expr.const]. f(x) is a constant expression because it's an invocation to a constexpr function.
Unless I am missing something, gcc and clang are correct here.

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.

Where in C++14 Standard does it say that a non-constexpr function cannot be used in a definition of a constexpr function?

For example the code below doesn't compile unless incr() is declared constexpr:
int incr(int& n) {
return ++n;
}
constexpr int foo() {
int n = 0;
incr(n);
return n;
}
Looking at §7.1.5/3 in C++14 we have:
The definition of a constexpr function shall satisfy the following
constraints:
(3.1) — it shall not be virtual (10.3);
(3.2) — its return type shall be a literal type;
(3.3) — each of its parameter types shall be a literal type;
(3.4) — its function-body shall be = delete, = default, or a compound-statement that does not contain
(3.4.1) — an asm-definition,
(3.4.2) — a goto statement,
(3.4.3) — a try-block, or
(3.4.4) — a definition of a variable of
non-literal type or of static or thread storage duration or for which
no initialization is performed.
Two paragraphs later, in [dcl.constexpr]/5:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.20), or, for a constructor, a constant
initializer for some object (3.6.2), the program is ill-formed; no diagnostic required.
No argument exists such that foo() could be a core constant expression because of incr(), therefore the program is ill-formed (NDR).
What you're looking for is § 5.19:
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:
This applies to the evaluation of an expression that is a constexpr function call. That is, calling a constexpr function will be a 'core constant expression' if evaluating the function, i.e. executing the body of the function according to the rules of the C++ abstract machine, doesn't do any of the things forbidden in the list given in § 5.19.
One of items in the list is:
an invocation of a function other than [...] a constexpr function
So to apply this to your example: evaluating the expression foo() evaluates a call to a function incr() which is not a constexpr function, which means that the expression foo() is not a core constant expression.
Further, since the above is true for all possible invocations of your function foo, the rule in § 7.1.5/5 kicks in and means that your example program is ill-formed, no diagnostic required, even if you never actually call foo().
As Ben Voigt points out a constexpr function can contain calls to non-consexpr functions, so long as the particular evaluation of the function does not actually evaluate any such function call (or it appears in a context that does not require a constant expression).
The restrictions in 5.19 only pertain to what expressions actually end up being evaluated as part of the evaluation of an expression.
For example:
#include <iostream>
int incr(int &n) { return ++n; }
enum E {be_constexpr, not_constexpr};
constexpr int foo(E e = be_constexpr) {
int n = 0;
if (e == not_constexpr) { incr(n); }
return n;
}
int main() {
constexpr int a = foo(); // foo() is a constant expression
int b = foo(not_constexpr); // may or may not evaluate `foo(non_constexpr)` at runtime. In practice modern C++ compilers will do compile-time evaluation here even though they aren't required to.
// constexpr int c = foo(not_constexpr); // Compile error because foo(not_constexpr) is not formally a constant expression, even though modern compilers can evaluate it at compile-time.
std::cout << a << ' ' << b << '\n';
}
It doesn't.
The following is allowed, even though it does exactly what you surmise is forbidden:
int incr(int& n) {
return ++n;
}
constexpr int foo(bool x) {
int n = 0;
if (x) incr(n);
return n;
}
The code in your question is disallowed by the rule with Barry quoted in his answer. But in my variation, there does exist a set of parameters (specifically, false) with which invocation of foo results in a compile-time constant expression.
Note that a diagnostic isn't required -- a conforming compiler could allow your version to compile as well.

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.