简体   繁体   中英

Converting a forwarding lambda to a function pointer

Here are two things that work. We can instantiate a forwarding function template to get a function pointer taking an lvalue:

template <class T>
void f(T &&) {}

void(*p)(int &) = f; // Cool!

We can also convert a non-capturing generic lambda taking an lvalue to a function pointer taking an lvalue:

auto l = [](auto &) { };

void (*lp)(int &) = l; // Still cool!

But apparently none of GCC and Clang will convert a forwarding generic lambda into a function pointer taking an lvalue:

auto l = [](auto &&) { };

void (*lp)(int &) = l; // Not cool!

GCC outputs:

<source>:9:21: error: invalid user-defined conversion from '<lambda(auto:1&&)>' to 'void (*)(int&)' [-fpermissive]
 void (*lp)(int &) = l;
                     ^

Clang outputs:

<source>:9:8: fatal error: no viable conversion from '(lambda at <source>:7:10)' to 'void (*)(int &)'
void (*lp)(int &) = l;
       ^            ~
<source>:7:10: note: candidate template ignored: could not match 'type-parameter-0-0 &&' against 'int &'
auto l = [](auto &&) { };
         ^

This is all despite the fact that a member function pointer taking an lvalue can be obtained from the forwarding lambda:

auto lmp = &decltype(l)::operator()<int &>;

template <class...>
struct check;
check<decltype(lmp)> c;

... which outputs the type void (<lambda(auto:1&&)>::*)(int&) const as expected.

I thought that reference collapsing rules were inherent to any template instantiation, and expected this to work. Do both Clang and GCC have a bug, or is that not actually provided by the Standard?

TL;DR: This is the specified behavior according to the standard. Template argument deduction has a special rule for deducing template arguments when taking the address of a function template, allowing forwarding references to work as expected. There is no such rule for conversion function templates.

Note: this looks like it's just an area for which nobody has written a proposal yet. If someone writes a proposal for this, it seems likely that this could be made to work in the future.


From [expr.prim.lambda] :

... . For a generic lambda with no lambda-capture, the closure type has a 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.

emphasis added

This states that the template arguments and function parameter types must be copied in a one-to-one manner:

// simplified version of the example in [expr.prim.lambda]/8
struct Closure {
    template <typename T>
    void operator()(T&& t) const {
        /* ... */
    }

    template <typename T>
    static void lambda_call_operator_invoker(T&& t) {
        Closure()(std::forward<T>(t));
    }

    // Exactly copying the template parameter list and function parameter types.
    template <typename T>
    using fn_type = void(*)(T&&);
    // using fn_type = void(*)(T); // this compiles, as noted later

    template <typename T>
    operator fn_type<T>() const {
        return &lambda_call_operator_invoker<T>;
    }
};

This fails to compile on all three of Clang, GCC, and MSVC, which can certainly be surprising, since we were expecting reference collapsing to happen on the T&& argument.

However, the standard doesn't support this.


The important parts of the standard are [temp.deduct.funcaddr] (deducing template arguments taking the address of a function template) and [temp.deduct.conv] (deducing conversion function template arguments). Critically, [temp.deduct.type] specifically mentions [temp.deduct.funcaddr] , but not [temp.deduct.conv] .

Some terms used in the standard:

  • P is the return type of the conversion template, or the type of the function template
  • A is the type we are "trying to convert to"

Similarly, if P has a form that contains (T), then each parameter type P i of the respective parameter-type-list ([dcl.fct]) of P is compared with the corresponding parameter type A i of the corresponding parameter-type-list of A. If P and A are function types that originated from deduction when taking the address of a function template ([temp.deduct.funcaddr]) or when deducing template arguments from a function declaration ([temp.deduct.decl]) and P i and A i are parameters of the top-level parameter-type-list of P and A, respectively, P i is adjusted if it is a forwarding reference ([temp.deduct.call]) and A i is an lvalue reference, in which case the type of P i is changed to be the template parameter type (ie, T&& is changed to simply T ).

emphasis added

This specifically calls out taking the address of a function template, making forwarding references just work. There is no similar reference to conversion function templates.

Revisiting the example earlier, if we change fn_type to void(*)(T) , that is the same operation that is described here in the standard.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM