7
   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:

Community
  • 1
  • 1
José Luis
  • 397
  • 1
  • 11

1 Answers1

4

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 4
    In other words, the *capture-default* case captures it to be safe, because you can't really tell if a dependent expression is going to be an odr-use or not until instantiation time, and it is just silly if `[=]` or `[&]` gives you a "missing capture" error. – T.C. Mar 05 '17 at 17:43
  • @Barry That's what I thought, but if that is correct my example will compile and behave exactly the same with or without capturing variable `x` (with or without *capture-default*): 1) Without *capture-default* variable `x` is not captured because it is not *odr-used* 2) With *capture-default* variable `x` is captured because of the dependent expresssion (even if it is not *odr-used* in any of the two versions of function `f()`!!). So capturing variable `x` depends (in this case) on whether the programer puts a *capture-default* or not, rather than on if the lambda "needs it" (*odr-uses*, etc.) – José Luis Mar 06 '17 at 11:03
  • 1
    @T.C. Does that mean that variable `x` should be captured just because of the dependen expression, even if there is no possible *odr-use*? By the way, I like your approach of “preemptive” capturing. I think I understand it better in that way. If there is a dependent expression that can make variable `x` be *odr-used*, variable `x` should be captured just in case, regardless if there is a *capture-default* or not. Is this correct? However I am not sure that the wording of the second bullet of Section 5.1.2, Paragraph 12 of the Standards helps to comply with that (tough wording for me though). – José Luis Mar 06 '17 at 11:06
  • I updated my post with a table and a question trying to summarize everything (maybe my misunderstanding comes from not knowing exactly what the compiler is able to know about the dependent expression) – José Luis Mar 06 '17 at 11:09
  • @JoséLuis I rolled back your edit - one question per question. Also the table was wrong, with a capture-default, `x` would be unconditionally captured because it's used in a dependent expression. – Barry Mar 06 '17 at 13:06
  • @Barry OK, I thought it was another point of the same main question, but I will post a new separate question. Regarding the table, I found it very useful to show all the possible cases, and I think it will help other people who face the same problem in the future. Can you confirm that it is fine if I edit it changing the case "Capture-Default"/"f(int, ...)#2" from "Does not capture x" to "Captures x"? Thanks for your help. – José Luis Mar 08 '17 at 08:53
  • 4
    @JoséLuis Capture is determined at the time you write the lambda, but the compiler can't figure out if a dependent expression involves an odr-use or not until it's actually instantiated. Therefore, with a capture-default (which essentially says "hi compiler, please capture everything this lambda needs"), the lambda will capture everything that can possibly be needed. OTOH, the programmer knows more about the code than the compiler, so if you know that there isn't going to be an odr-use, you can omit the capture, and the compiler won't stand in your way until you actually try to odr-use it. – T.C. Mar 08 '17 at 18:19