This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 113d. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-04-05


2561. Conversion to function pointer for lambda with explicit object parameter

Section: 7.5.5.2  [expr.prim.lambda.closure]     Status: review     Submitter: Barry Revzin     Date: 2022-02-14     Liaison: EWG

P0847R7 (Deducing this) (approved October, 2021) added explicit-object member functions. Consider:

  struct C {
    C(auto) { }
  };

  void foo() {
    auto l = [](this C) { return 1; };
    void (*fp)(C) = l;
    fp(1); // same effect as decltype(l){}() or decltype(l){}(1) ?
  }

Subclause 7.5.5.2 [expr.prim.lambda.closure] paragraph 8 does not address explicit object member functions:

The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (9.11 [dcl.link]) having the same parameter and return types as the closure type's function call operator. The conversion is to “pointer to noexcept function” if the function call operator has a non-throwing exception specification. The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type. F is a constexpr function if...

Suggested resolution [SUPERSEDED]:

  1. Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 8 as follows:

    ... The value returned by this conversion function is
    • for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the function call operator (7.6.2.2 [expr.unary.op];
    • otherwise, the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type.
    F is a constexpr function if... is an immediate function.

    [ Example:

      struct C {
        C(auto) { }
      };
    
      void foo() {
        auto a = [](C) { return 0; };
        int (*fp)(C) = a;   // OK
        fp(1);              // same effect as decltype(a){}(1)
        auto b = [](this C) { return 1; };
        fp = b;             // OK
        fp(1);              // same effect as (&decltype(b)::operator())(1)
      }
    

    -- end example ]

  2. Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 11 as follows:

    The value returned by any given specialization of this conversion function template is
    • for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the corresponding function call operator template specialization (7.6.2.2 [expr.unary.op]);
    • otherwise, the address of a function F that, when invoked, has the same effect as invoking the generic lambda's corresponding function call operator template specialization on a default-constructed instance of the closure type.
    F is a constexpr function if...

CWG 2023-06-17

Requesting guidance from EWG with paper issue 1689.

Additional notes (October, 2023)

Additional examples demonstrating implementation divergence between clang and MSVC:

  struct Any { Any(auto) {} };
  auto x = [](this auto self, int x) { return x; };
  auto y = [](this Any self, int x) { return x; };
  auto z = [](this int (*self)(int), int x) { return x; };
  int main() {
    x(1);
    y(1);
    z(1);
    int (*px)(int) = +x; // MSVC
    int (*py1)(int) = +y; // MSVC
    int (*py2)(Any, int) = +y; // Clang
    int (*pz1)(int) = +z; // MSVC
    int (*pz2)(int (*)(int), int) = +z; // Clang
  }

Additional notes (November, 2023)

Additional example:

  auto c2 = [](this auto self) { return sizeof(self); };
  struct Derived2 : decltype(c) { int value; } d2;
  struct Derived3 : decltype(c) { int value[10]; } d3;

For MSVC, d2() == 4 and d3() == 40, but +d2 and +d3 both point to functions returning 1.

EWG 2023-11-07

Move forward with option 1 "punt" from D3031 for C++26. A future paper can explore other solutions.

Proposed resolution (reviewed by CWG 2023-11-09):

  1. Change the example in 7.5.5.1 [expr.prim.lambda.general] paragraph 6 as follows:

      int i = [](int i, auto a) { return i; }(3, 4);  // OK, a generic lambda
      int j = []<class T>(T t, int i) { return i; }(3, 4);  // OK, a generic lambda
      auto x = [](int i, auto a) { return i; };             // OK, a generic lambda
      auto y = [](this auto self, int i) { return i; };      // OK, a generic lambda
      auto z = []<class T>(int i) { return i; };             // OK, a generic lambda
    
    
  2. Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 9 as follows:

    The closure type for a non-generic lambda-expression with no lambda-capture and no explicit object parameter (9.3.4.6 [dcl.fct]) whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (9.11 [dcl.link]) having the same parameter and return types as the closure type's function call operator. ...
  3. Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 10 as follows:

    For a generic lambda with no lambda-capture and no explicit object parameter (9.3.4.6 [dcl.fct]), the closure type has a conversion function template to pointer to function. ...

CWG 2023-11-09

Keeping in review status in anticipation of a paper proposing reasonable semantics for the function pointer conversions.

EWG 2024-03-18

Progress with option #1 of P3031R0.