N3649

Generic (Polymorphic) Lambda Expressions (Revision 3)

Document no: N3649

Faisal Vali                 Herb Sutter                 Dave Abrahams

2013-04-19

1         Introduction

This document revises wording for generic lambda expressions as described in N3559: Proposal for Generic (Polymorphic) Lambda Expressions (Revision 2) and as approved by EWG and which should be referenced for further detail.  Today’s C++11 lambda expression concisely creates an instance of a class having a non-template function call operator.  We propose a pure extension of C++11 lambda syntax that creates an instance of a class having a function call operator template.  

 

Here follow some examples of the proposed syntax extension in use:

   

 // 'Identity' is a lambda that accepts an argument of any type and

 // returns the value of its parameter.  

auto Identity = [](auto a) { return a; };

int three = Identity(3);

char const* hello = Identity("hello");

 // Conversion to function pointer for capture-less lambdas

int (*fpi)(int) = Identity;

char (*fpc)(char) = Identity;       

2         Proposal

We are proposing the following pure extensions to C++11:

  1. Allow auto type-specifier to indicate a generic lambda parameter
  2. Allow conversion from a capture-less generic lambda to an appropriate pointer-to-function

Each of these is discussed in further detail in N3559.

 

3      Changes from N3559  

  1. Added wording to support variadic auto parameters

  1. Return type deduction of the pointer to function conversion operator wording has been reworded in terms of decltype

  1. Specified the definition of implicit capture using a list and incorporated the change from N3648.  Applied wording for capturing variables named in expressions dependent on a generic lambda parameter to a non-generic lambda (so that if it is present within a nested chain it too can capture appropriately)

  1. Acknowledge return type deduction of placeholder types

  1. Remove redundant (already covered in 5.1.2 paragraph 11) and potentially misplaced wording in 5.1.2 paragraph 12 about certain dependent expressions capturing variables AND instead add a phrase reinforcing that if instantiation of the function call operator template odr-uses a variable, it needs to be capture

  1. Added additional examples and notes

  1. unhyphenated pointer-to-function; hyphenated and italicized trailing-return-type; potentially-evaluated has been un-italicized, requested commas have been added

NOTE: Wording that has been added as compared to N3559 is a darker shade of green

Changes to the Working Draft

Change in 7.1.6.4  dcl.spec.auto paragraph 1:

The auto type-specifier signifies that the type of a variable being declared shall be deduced from its initializer, or that a function declarator shall include a trailing-return-type, or that a lambda is a generic lambda.

Change in 7.1.6.4  dcl.spec.auto, add after paragraph 2:

Otherwise, if the auto type-specifier appears as one of the decl-specifiers in the decl-specifier-seq of a parameter-declaration of a lambda-expression, the lambda is a generic lambda (5.1.2 expr.prim.lambda). [ Example:    

  auto glambda = [](int i, auto a) { return i; }; // OK: a generic lambda

 end example]

Change in 5.1.2  expr.prim.lambda paragraph 5:

The closure type for a non-generic lambda-expression has a public inline function call operator (13.5.4) whose parameters and return type are described by the lambda-expression’s parameter-declaration-clause and trailing-return-type respectively.   For a generic lambda, the closure type has a public inline function call operator member template (14.5.2 temp.mem) whose template-parameter-list consists of one invented type template-parameter for each occurrence of auto in the lambda's parameter-declaration-clause, in order of appearance.  The invented type template-parameter is a parameter pack if the corresponding parameter-declaration declares a function parameter pack (8.3.5 dcl.fct).  The return type and function parameters of the function call operator template are derived from the lambda-expression's trailing-return-type and parameter-declaration-clause by replacing each occurrence of auto in the decl-specifiers of the parameter-declaration-clause with the name of the corresponding invented template-parameter. [ Example:

    auto glambda  = [](auto a, auto&& b) { return  a < b; };

  bool b = glambda(3, 3.14);       // OK

  auto vglambda = [](auto printer) {

     return [=](auto&& ... ts) {   // OK: ts is a  function parameter pack

         printer(std::forward<decltype(ts)>(ts)...);

         

         return [=]() {

           printer(ts ...);

         };

     };

  };

  auto p = vglambda( [](auto v1, auto v2, auto v3)    

                         { std::cout << v1 << v2 << v3; } );

  auto q = p(1, 'a', 3.14);  // OK: outputs 1a3.14

  q();                       // OK: outputs 1a3.14

 end example] This function call operator or operator template is declared const (9.3.1) if and only if the lambda expression’s parameter-declaration-clause is not followed by mutable. It is neither virtual nor declared volatile. Default arguments (8.3.6) shall not be specified in the parameter-declaration-clause of a lambda declarator.  Any exception-specification specified on a lambda-expression applies to the corresponding function call operator or operator template. An attribute-specifier-seq in a lambda-declarator appertains to the type of the corresponding function call operator or operator template. [ Note: Names referenced in the lambda-declarator are looked up in the context in which the lambda-expression appears. —end note ]

Change in 5.1.2  expr.prim.lambda paragraph 6:

The closure type for a non-generic lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator.  The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.  For a generic lambda with no lambda-capture, the closure type has a public non-virtual non-explicit const conversion function template to pointer to function.  The conversion function template has the same invented template-parameter-list, and the pointer to function has the same parameter types, as the function call operator template.  The return type of the pointer to function shall behave as if it were a decltype-specifier denoting the return type of the corresponding function call operator template specialization.  [ Note: If the generic lambda has no trailing-return-type or the trailing-return-type contains a placeholder type, return type deduction of the corresponding function call operator template specialization has to be done.  The corresponding specialization is that instantiation of the function call operator template with the same template arguments as those deduced for the conversion function template.  Consider the following:

 

  auto glambda = [](auto a) { return a; };

  int (*fp)(int) = glambda;

 

The behavior of the conversion function of the glambda above is like that of the following conversion function:

  struct Closure {

    template<class T> auto operator()(T t) const { ... }

    template<class T> static auto lambda_call_operator_invoker(T a) {

      // forwards execution to operator()(a) and therefore has

      // the same return type deduced                                  

      ...

    }

    template<class T> using fptr_t =

       decltype(lambda_call_operator_invoker(declval<T>())) (*)(T);

   

    template<class T> operator fptr_t<T>() const

      { return &lambda_call_operator_invoker; }

  }; end note]

[ Example:

 

  void f1(int (*)(int))   { }

  void f2(char (*)(int))  { }

 

  void g(int (*)(int))    { }  // #1

  void g(char (*)(char))  { }  // #2

 

  void h(int (*)(int))    { }   // #3

  void h(char (*)(int))   { }   // #4

 

 

  auto glambda = [](auto a) { return a; };

  f1(glambda);  // OK

  f2(glambda);  // error: ID is not convertible

  g(glambda);   // error: ambiguous

  h(glambda);   // OK: calls #3 since it is convertible from ID

   int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK

 

 — end example] The value returned by any given specialization of this conversion function template shall be the address of a function that, when invoked, has the same effect as invoking the generic lambda's corresponding function call operator template specialization. [ Note: This will result in the implicit instantiation of the generic lambda's body.  The instantiated generic lambda's return type and parameter types shall match the return type and parameter types of the pointer to function   end note ] [ Example:

  auto GL = [](auto a) { std::cout << a; return a; };

  int (*GL_int)(int) = GL;  // OK: through conversion function template

  GL_int(3);                // OK: same as GL(3)   

 end example]   

Change in 5.1.2 expr.prim.lambda paragraph 11:

If a lambda-expression has an associated capture-default and its compound-statement odr-uses (3.2) this or a variable with automatic storage duration and the odr-used entity is not explicitly captured, then the odr-used entity is said to be implicitly captured.

A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration INSERT from N3648: (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 basic.def.odr) 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 ]

Change in 5.1.2 expr.prim.lambda paragraph 12:

An entity is captured if it is captured explicitly or implicitly. An entity captured by a lambda-expression is odr-used (3.2) 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 (3.2) 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.

Change in 8.3.5 dcl.fct paragraph 14:

There is a syntactic ambiguity when an ellipsis occurs at the end of a parameter-declaration-clause without a preceding comma. In this case, the ellipsis is parsed as part of the abstract-declarator if the type of the parameter either names a template parameter pack that has not been expanded or contains auto; otherwise, it is parsed as part of the parameter-declaration-clause.

Change in 14.5.2 temp.mem paragraph 2:

A local class of non-closure type shall not have member templates.


6         Acknowledgments

The authors would like to thank:

Jens Maurer for his indispensable assistance in writing the initial wording that accompanied the proposal N3559 and continued guidance.

Doug Gregor for his continued guidance in delineating the semantics of capturing within generic lambdas, and for suggesting wording to support variadic packs.

Richard Smith for rescuing the conversion operator from being prohibited in generic lambdas with no trailing-return-type (by suggesting wording using decltype semantics).

Christof Meerwald and Richard Corden for identifying, in the initial wording, an ambiguity with the conversion to pointer to function.

John Spicer and Daveed Vandevoorde for personally (off-hours) reviewing sections of the wording and providing precious feedback.

Jason Merrill for his guidance with initial drafts of this wording.

All the members of the Core subgroup who carefully provided valuable feedback during continued review of this wording.