将转发 lambda 转换为函数指针
Converting a forwarding lambda to a function pointer
这里有两个有用的东西。我们可以实例化一个转发函数模板,得到一个取左值的函数指针:
template <class T>
void f(T &&) {}
void(*p)(int &) = f; // Cool!
我们还可以将采用左值的非捕获泛型 lambda 转换为采用左值的函数指针:
auto l = [](auto &) { };
void (*lp)(int &) = l; // Still cool!
但显然 none 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 是否都有错误,或者标准实际上没有提供错误?
TL;DR:这是根据标准指定的行为。模板参数推导有一个特殊的规则,用于在获取函数模板的地址时推导模板参数,从而允许转发引用按预期工作。转换函数模板无此规则
注意:这看起来只是一个还没有人写提案的领域。如果有人为此写了一个提案,这似乎有可能在未来发挥作用。
... . 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.
强调
这表明模板参数和函数参数类型必须以一对一的方式复制:
// 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] (deducing template arguments taking the address of a function template) and [temp.deduct.conv] (deducing conversion function template arguments). Critically, [temp.deduct.type]特别提到[temp.deduct.funcaddr],但没有[temp.deduct.conv ].
标准中使用的一些术语:
- P为转换模板的return类型,或函数模板的类型
- A是我们的类型"trying to convert to"
Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list ([dcl.fct]) of P is compared with the corresponding parameter type Ai 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 Pi and Ai are parameters of the top-level parameter-type-list of P and A, respectively,
Pi is adjusted if it is a forwarding reference ([temp.deduct.call]) and Ai is an lvalue reference, in which case the type of Pi is changed to be the template parameter type (i.e., T&&
is changed to simply T
).
强调
这专门调用了函数模板的地址,使转发引用正常工作。没有类似的转换函数模板参考。
回顾之前的例子,如果我们将 fn_type
更改为 void(*)(T)
,这与标准中描述的操作相同。
这里有两个有用的东西。我们可以实例化一个转发函数模板,得到一个取左值的函数指针:
template <class T>
void f(T &&) {}
void(*p)(int &) = f; // Cool!
我们还可以将采用左值的非捕获泛型 lambda 转换为采用左值的函数指针:
auto l = [](auto &) { };
void (*lp)(int &) = l; // Still cool!
但显然 none 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 是否都有错误,或者标准实际上没有提供错误?
TL;DR:这是根据标准指定的行为。模板参数推导有一个特殊的规则,用于在获取函数模板的地址时推导模板参数,从而允许转发引用按预期工作。转换函数模板无此规则
注意:这看起来只是一个还没有人写提案的领域。如果有人为此写了一个提案,这似乎有可能在未来发挥作用。
... . 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.
强调
这表明模板参数和函数参数类型必须以一对一的方式复制:
// 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] (deducing template arguments taking the address of a function template) and [temp.deduct.conv] (deducing conversion function template arguments). Critically, [temp.deduct.type]特别提到[temp.deduct.funcaddr],但没有[temp.deduct.conv ].
标准中使用的一些术语:
- P为转换模板的return类型,或函数模板的类型
- A是我们的类型"trying to convert to"
Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list ([dcl.fct]) of P is compared with the corresponding parameter type Ai 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 Pi and Ai are parameters of the top-level parameter-type-list of P and A, respectively, Pi is adjusted if it is a forwarding reference ([temp.deduct.call]) and Ai is an lvalue reference, in which case the type of Pi is changed to be the template parameter type (i.e.,
T&&
is changed to simplyT
).
强调
这专门调用了函数模板的地址,使转发引用正常工作。没有类似的转换函数模板参考。
回顾之前的例子,如果我们将 fn_type
更改为 void(*)(T)
,这与标准中描述的操作相同。