Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependent Expression and ODR-use in a Generic Lambda in C++14

   void f(      int , const int (&)[2] = {}) { } // #1 
   void f(      int , const int (&)[1]     ) { } // #2 
// void f(const int&, const int (&)[1]     ) { } // #2_original 

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 ??? 
    }; 
} 

This is an example from the C++14 Standard (ISO/IEC 14882:2014), Section 5.1.2, Paragraph 12, which I modified in 2 ways:

  • First, both versions of function f() have an int as the first argument, so variable x is not odr-used in any case.
  • Second, I removed (commented out) the capture-default in lambda g2.

Is this code Standard-compliant? Both clang and gcc compile successfully. However, in the original example lambda g2 had a capture-default ([=]), so variable x was implicitly captured because there was a dependent expression (and also because it could be odr-used in function f() #2_original). Note that in the aforementioned paragraph of the Standard there are 2 conditions to implicitly capture variable x (odr-use OR dependent expression). Now, without capture-default nor odr-use:

  • Shouldn't this be a compile-time error since there is a dependent expression and no capture-default? That is, variable x needs to be captured but it cannot be (assume g2 calls with both kinds of arguments, i.e., ones yielding sizeof(a)=1 and others sizeof(a)>1).

  • Or does the condition of the dependent expression to implicitly capture a variable only apply when there is a capture-default? That would mean that, with no odr-use (i.e., without const int& in function f() #2) the program will work in the same way regardless of the capture-default. Therefore, wouldn't the second condition about the dependent expression be useless?

This is the C++14 Standard (ISO/IEC 14882:2014), Section 5.1.2, Paragraph 12 (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 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.

Note: lambda g does not capture variable x because it is not odr-used in f(x) (see C++14 Standard (ISO/IEC 14882:2014), Section 5.1.2, Paragraph 13)

Links:

  • N3649: Generic (Polymorphic) Lambda Expressions (Revision 3)

  • Using of not captured variable in lambda

like image 747
José Luis Avatar asked Mar 03 '17 15:03

José Luis


1 Answers

Is this code Standard-compliant?

Yes. The rule 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.

Does the lambda odr-use x? No, because of [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 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 an integral constant that is used in an expression where the lvalue-to-rvalue conversion is applied, so it is not odr-used. Since it's not odr-used, it's not an error that you aren't capturing it.

It would be ill-formed if there was an overload of f that took its first argument by reference - that instantiation of the call operator would odr-use x, but it's not captured, making it ill-formed.


The section you cite is irrelevant for your modified example. It only refers to "A lambda-expression with an associated capture-default". But your lambda doesn't have a capture-default. A capture-default is either = or &, the introducer [] has no capture-default. However, if we had [=] or [&], that section would explain why x would be captured.

like image 54
Barry Avatar answered Sep 22 '22 23:09

Barry