将转发lambda转换为函数指针

Que*_*tin 15 c++ templates function-pointers language-lawyer generic-lambda

这有两件事可行.我们可以实例化一个转发函数模板来获取一个带左值的函数指针:

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

void(*p)(int &) = f; // Cool!
Run Code Online (Sandbox Code Playgroud)

我们还可以将带有左值的非捕获通用lambda转换为带左值的函数指针:

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

void (*lp)(int &) = l; // Still cool!
Run Code Online (Sandbox Code Playgroud)

但显然GCC和Clang都没有将转发通用lambda转换为带左值的函数指针:

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

void (*lp)(int &) = l; // Not cool!
Run Code Online (Sandbox Code Playgroud)

GCC产出:

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

Clang输出:

<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 &&) { };
         ^
Run Code Online (Sandbox Code Playgroud)

尽管可以从转发lambda 获取一个带左值的成员函数指针,但这一切都是这样的:

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

template <class...>
struct check;
check<decltype(lmp)> c;
Run Code Online (Sandbox Code Playgroud)

... void (<lambda(auto:1&&)>::*)(int&) const按预期输出类型.

我认为参考折叠规则是任何模板实例化所固有的,并且期望它能够工作.Clang和GCC都有错误,或标准实际上没有提供?

Jus*_*tin 8

TL; DR:这是根据标准的指定行为.模板参数推导有一个特殊的规则,用于在获取函数模板的地址时推导模板参数,允许转发引用按预期工作.转换函数模板没有这样的规则.

注意:这看起来只是一个尚未编写提案的领域.如果有人为此撰写提案,似乎可能会在将来工作.


来自[expr.prim.lambda]:

...... 对于没有lambda-capture的通用lambda,闭包类型有一个指向函数的转换函数模板.转换函数模板具有相同的发明模板参数列表,并且指向函数的指针具有与函数调用操作符模板相同的参数类型.指向函数的指针的返回类型应该表现为它是一个decltype-specifier,表示相应函数调用操作符模板特化的返回类型.

重点补充

这表明必须以一对一的方式复制模板参数和函数参数类型:

// 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>;
    }
};
Run Code Online (Sandbox Code Playgroud)

这无法在Clang,GCC和MSVC的所有三个上进行编译,这当然是令人惊讶的,因为我们期望参数崩溃发生在T&&争论上.

但是,该标准不支持这一点.


标准的重要部分是[temp.deduct.funcaddr](使用函数模板的地址推导模板参数)和[temp.deduct.conv](推导转换函数模板参数).关键的是,[temp.deduct.type]特别提及[temp.deduct.funcaddr],但不提及[temp.deduct.conv].

标准中使用的一些术语:

  • P是转换模板的返回类型,或函数模板的类型
  • A是我们"试图转换为"的类型

类似地,如果P具有包含(T)的形式,则将P 的相应参数类型列表([dcl.fct])的每个参数类型P i与相应参数的相应参数类型A i进行比较 - A的类型列表.如果P和A是在获取函数模板的地址([temp.deduct.funcaddr])时从演绎中产生的函数类型,或者是从函数声明中推导出模板参数([temp.deduct.decl] ])和P i和A i分别是P 和A 的顶级参数类型列表的参数,如果它是转发参考([temp.deduct.call])则调整P i并且A i是左值参考,在这种情况下,P i的类型被改变为模板参数类型(即,T&&简单地改变T).

重点补充

这特别要求获取函数模板的地址,使转发引用正常工作.转换函数模板没有类似的引用.

重新访问前面的示例,如果我们fn_type改为void(*)(T),那就是标准中描述的相同操作.