将转发lambda转换为函数指针

时间:2018-05-19 17:50:16

标签: c++ templates language-lawyer function-pointers generic-lambda

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

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

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

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

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

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

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

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

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

GCC产出:

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

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 &&) { };
         ^

尽管从转发lambda中获取了一个可以获取左值的成员函数指针,但这都是事实:

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

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

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

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

1 个答案:

答案 0 :(得分: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>;
    }
};

这无法在all three of Clang, GCC, and 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的相应参数类型列表的相应参数类型A i 如果P和A是在获取函数模板的地址时从演绎中产生的函数类型([temp.deduct] .funcaddr])或从函数声明([temp.deduct.decl])和P i 和A i 中推导模板参数时,是顶级参数的参数-type-list of P和A,分别为   如果它是转发引用([temp.deduct.call])并且A i 是左值引用,则调整P i ,在这种情况下P i 更改为模板参数类型(即T&&更改为T)。

强调添加

这特别指出了获取函数模板的地址,使转发引用正常工作。没有类似的转换函数模板参考。

如果我们将fn_type更改为void(*)(T),请重新访问前面的示例,这与标准中描述的操作相同。