15

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

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;
                     ^

叮当输出:

<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 是否都有错误,或者标准实际上没有提供错误?

4

1 回答 1

9

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

注意:这看起来只是一个还没有人写过提案的领域。如果有人为此写了一份提案,那么这似乎很可能在未来发挥作用。


来自[expr.prim.lambda]

... . 对于没有 lambda 捕获的通用 lambda,闭包类型有一个转换函数模板来指向函数的指针。转换函数模板与函数调用运算符模板具有相同的发明模板参数列表,并且指向函数的指针具有相同的参数类型。指向函数的指针的返回类型应该表现得好像它是一个 decltype 说明符,表示相应函数调用运算符模板特化的返回类型。

强调添加

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

// 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>;
    }
};

这在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 的顶级参数类型列表的参数,如果 P i是转发引用([temp.deduct.call]),则调整 P i 并且 A i是一个左值引用,在这种情况下是 P 的类型i更改为模板参数类型(即T&&更改为 simple T)。

强调添加

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

重温前面的示例,如果我们更改fn_typevoid(*)(T),则与标准中此处描述的操作相同。

于 2018-05-19T18:30:19.990 回答