使用可变参数模板参数解析 lambda 签名
Using variadic template arguments to resolve a lambda signature
虽然有很多关于获取任何模板化回调函数/方法(当然包括lambda(的返回类型的东西,但我很难找到有关解析lambda函数的完整调用签名的信息。至少在 gcc 4.7 中,这似乎是一个正常技巧(见下文(不起作用的边缘情况。这是我正在尝试做的事情,到目前为止(当然是精简版本(......
template<typename Sig>
struct invokable_type { };
template<typename R, typename...As>
struct invokable_type<R(As...)> {
static constexpr size_t n = sizeof...(As);
typedef R(callable_type)(As...);
template<size_t i>
struct arg {
typedef typename peel_type<i, As...> type;
};
};
为了简洁起见,这里不包括peel_type<size_t, typename...>
,但它是一个简单的参数类型剥皮器(我认为 C++11 中内置了一个,但我从来没有费心去看(。这个问题并不重要。
无数的可调用类型,例如R(*)(As...)
,R(&)(As...)
,(R(T::*)(As...)
,std::function<R(As...)>
,方法cv限定符,方法左值/右值限定符等,等等,等等。
然后,在路上的某个地方,我们有一个可爱的函数或方法(这里的函数,没关系(,看起来像......
template<typename C, typename...As>
static void do_something(C&& callback, As&&...as) {
do_something_handler<invokable_type<C>::n, As...>::something(std::forward<C>(callback), std::forward<As>(as)...);
}
别管do_something_handler
做什么...这完全无关紧要。问题出在 lambda 函数上。
对于我专门研究的所有可能的通用可调用签名(似乎除了非 STL 函子之外的所有签名(,当将它们作为第一个参数调用do_something()
时,这工作得很好(模板推导完全有效(。但是,lambda 函数会导致未捕获的类型签名,从而导致使用invokable_type<Sig>
,这意味着根本不存在 ::n
和 ::args<0>::type
之类的东西。
没问题的例子...
void something(int x, int y) {
return x * y;
}
。后来...
do_something(something, 7, 23);
问题示例...
do_something([](int x, int y) {
return x * y;
}, 7, 23);
如果我正确理解 lambda 函数,编译器可能会将此 lambda 编译为定义范围的"命名空间"内的静态函数(gcc 当然似乎是(。对于我的生活,我无法弄清楚签名实际上是什么。看起来它肯定有一个应该可以通过模板专用化(基于错误报告(来推断的。
另一个无关紧要的问题是,即使有我可以使用的签名,交叉编译器有多危险?lambda 编译签名是标准化的还是全面的?
总结和扩展评论:
根据 [expr.prim.lambda]/3,lambda-expression 的类型是一个类类型,就像"普通的命名函数对象类型"一样:
lambda表达式的类型(也是闭包对象的类型(是一种唯一的、未命名的非联合类类型——称为闭包类型 [...]
再往下,/5 指定:
lambda 表达式的闭包类型具有公共
inline
函数调用运算符 (13.5.4(,其参数和返回类型分别由 lambda 表达式的参数声明子句和尾随返回类型描述。当且仅当 lambda 表达式的参数声明子句后不跟可变子句时,此函数调用运算符声明const
(9.3.1(。它既不是虚拟的,也不是声明的volatile
.[...]
(然后通过指定属性和异常规范继续(
这意味着 lambda [](int p){ return p/2.0; }
在这方面的行为与
struct named_function_object
{
double operator() (int p) const { return p/2.0; }
};
因此,您的第一个专业
template<typename R, typename...As>
struct invokable_type<R(As...)>;
应该已经能够处理 lambda。SSCCE
#include <utility>
template<class T>
struct decompose;
template<class Ret, class T, class... Args>
struct decompose<Ret(T::*)(Args...) const>
{
constexpr static int n = sizeof...(Args);
};
template<class T>
int deduce(T t)
{
return decompose<decltype(&T::operator())>::n;
}
struct test
{
void operator() (int) const {}
};
#include <iostream>
int main()
{
std::cout << deduce(test{}) << std::endl;
std::cout << deduce([](int){}) << std::endl;
}
在最新版本的 clang++ 和 g++ 上编译良好。似乎问题与g ++ 4.7有关
进一步的研究表明,g++-4.7.3编译了上面的例子。
该问题可能与 lambda 表达式将生成函数类型的误解有关。如果我们do_something
定义为
template<class C>
void do_something(C&&)
{
std::cout << invokable_type<C>::n << std::endl;
}
然后对于像 do_something( [](int){} )
这样的调用,模板参数 C
将被推导出为闭包类型(无引用(,即类类型。上面定义的struct test
的类似情况是do_something( test{} )
,在这种情况下,C
将被推导出为test
。
因此,实例化的invokable_type
的专业化是一般情况
template<class T>
struct invokable_type;
因为T
这两种情况下都不是像指针或函数类型那样的"复合类型"。通过假设它只采用纯类类型,然后使用该类类型的成员T::operator()
,可以使用这种一般情况:
template<class T>
struct invokable_type
{
constexpr static int n = invokable_type<&T::operator()>::n;
};
或者,正如Potatoswatter所说,通过继承
template<class T>
struct invokable_type
: invokable_type<&T::operator()>
{};
然而,Potatoswatter的版本更通用,可能更好,依赖于SFINAE检查是否存在T::operator()
,如果找不到操作员,这可以提供更好的诊断信息。
:注:如果您在不捕获任何内容的 lambda 表达式前面加上一元+
,它将被转换为指向函数的指针。 do_something( +[](int){} )
将与专业化invokable_type<Return(*)(Args...)>
合作。
正如 DyP 所提到的,lambda 函子保证具有公共operator ()
。请注意,operator ()
不能是static
或非会员。
(由于存在函数指针类型的转换运算符,类型也可以是可调用的,并且无状态 lambda 确实有这样的转换运算符,但它们仍然必须提供operator ()
。
您可以使用 decltype( & T::operator() )
获取operator ()
的签名,前提是只有一个重载,这对于 lambda 来说是保证的。这将生成指向成员函数类型的指针。您可以使用元函数去掉T::
部分,或者直接针对 PTMF 编写元函数查询。
#include <iostream>
#include <typeinfo>
#include <type_traits>
#include <tuple>
template< typename t, std::size_t n, typename = void >
struct function_argument_type;
template< typename r, typename ... a, std::size_t n >
struct function_argument_type< r (*)( a ... ), n >
{ typedef typename std::tuple_element< n, std::tuple< a ... > >::type type; };
template< typename r, typename c, typename ... a, std::size_t n >
struct function_argument_type< r (c::*)( a ... ), n >
: function_argument_type< r (*)( a ... ), n > {};
template< typename r, typename c, typename ... a, std::size_t n >
struct function_argument_type< r (c::*)( a ... ) const, n >
: function_argument_type< r (c::*)( a ... ), n > {};
template< typename ftor, std::size_t n >
struct function_argument_type< ftor, n,
typename std::conditional< false, decltype( & ftor::operator () ), void >::type >
: function_argument_type< decltype( & ftor::operator () ), n > {};
int main() {
auto x = []( int, long, bool ){};
std::cout << typeid( function_argument_type< decltype(x), 0 >::type ).name() << 'n';
std::cout << typeid( function_argument_type< decltype(x), 1 >::type ).name() << 'n';
std::cout << typeid( function_argument_type< decltype(x), 2 >::type ).name() << 'n';
}
http://coliru.stacked-crooked.com/a/57cd7bb76267ffda
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 如何将lambda作为模板类的成员函数参数
- 使用自动推导的 lambda 参数作为常量表达式
- 如果模板没有可变参数,则 Lambda 被推导出为 std::function
- 将有状态的 lambda 传递到 C 样式函数中,而无需上下文参数
- 引用捕获和在 lambda 中通过引用发送参数有什么区别 (C++)
- 将__device__ lambda 作为参数传递给 __global__ 函数
- 将 lambda 函数作为参数传递C++
- 如何将 lambda 函数作为参数发送到另一个函数
- Lambda可以用作非类型模板参数吗
- 将参数传递给泛型 lambda 时复制构造函数不正确
- 如何确定捕获不可复制参数的 lambda 的类型?
- 省略C++可变参数 lambda 中的"auto"关键字?
- 如何访问可变参数 lambda 函数参数
- 可变参数 lambda 捕获的解决方法
- 传递给多个参数 lambda 表达式的参数
- 如何编写丢弃其参数的通用可变参数 lambda
- 使用c++参数lambda函数
- 将函数模板"pass"为泛型可变参数 lambda 返回语句是好方法吗?
- c++模板非类型参数lambda函数