Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 478
eg0x20 Avatar asked Dec 16 '15 22:12

eg0x20


People also ask

Are lambda captures Const?

If not provided, the objects captured by copy are const in the lambda body. The following specifiers are allowed at most once in each sequence: mutable : allows body to modify the objects captured by copy, and to call their non-const member functions.

What is the difference between double and const double?

There is no difference.

What does const double mean?

Declaring a variable to be const means that its value cannot change; therefore it must be given a value in its declaration: const double TEMP = 98.6; // TEMP is a const double. ( Equivalent: double const TEMP = 98.6; )

What is the type of lambda function in C++?

A lambda is an object (hence why we're referring to it as a functor, rather than a function) so has a type and can be stored. However, the type of the lambda is only known by the compiler (since it is compiler-generated), so you must use auto for declaration instances of the lambda.


1 Answers

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.

like image 167
Shafik Yaghmour Avatar answered Sep 27 '22 20:09

Shafik Yaghmour