这有两件事可行。我们可以实例化一个转发函数模板来获取一个带左值的函数指针:
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都有错误,或标准实际上没有提供?
答案 0 :(得分:8)
TL; DR:这是根据标准的指定行为。模板参数推导有一个特殊的规则,用于在获取函数模板的地址时推导模板参数,允许转发引用按预期工作。转换函数模板没有这样的规则。
注意:这看起来只是一个尚未编写提案的领域。如果某人为此撰写提案,则可能会在未来将其用于此工作。
......对于没有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具有包含(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)
,请重新访问前面的示例,这与标准中描述的操作相同。