Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get -Wunused-lambda-capture only for bool and int but not double? [duplicate]

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 479
eg0x20 Avatar asked Dec 16 '15 22:12

eg0x20


People also ask

Is bool more efficient than int?

Using a bool IMO reflects its use much better than using an int . In fact, before C++ and C99, C89 didn't have a Boolean type. Programmers would often typedef int Bool in order to make it clear that they were using a boolean.

Can a bool be 1 or 0 C++?

For this, C++ has a bool data type, which can take the values true (1) or false (0).

How do you set a Boolean variable to be false?

To declare a Boolean variable, we use the keyword bool. To initialize or assign a true or false value to a Boolean variable, we use the keywords true and false. Boolean values are not actually stored in Boolean variables as the words “true” or “false”.

Does C++ equal 1 true?

In C++, the data type bool has been introduced to hold a boolean value, true or false. The values true or false have been added as keywords in the C++ language. Important Points: The default numeric value of true is 1 and false is 0.


2 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 160
Shafik Yaghmour Avatar answered Oct 08 '22 19:10

Shafik Yaghmour


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.

like image 31
101010 Avatar answered Oct 08 '22 17:10

101010