虽然有很多关于获取任何模板化回调函数/方法(当然包括lambdas)的返回类型的东西,但是我很难找到解决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,但我从不打扰看)。这个问题并不重要。
然后,当然,对于无数可调用类型存在特化(以及其他属性/ typedef),例如R(*)(As...)
,R(&)(As...)
,(R(T::*)(As...)
,std::function<R(As...)>
,方法cv限定符,方法lvalue / rvalue限定符等等。
然后,在某个地方,我们有一个可爱的功能或方法(这里的功能,无关紧要),看起来像......
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()
作为第一个参数调用invokable_type<Sig>
时(模板演绎完全有效),这种方法很有效。但是,lambda函数会导致未捕获的类型签名,导致使用::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);
问题示例......
{{1}}
如果我正确理解lambda函数,编译器可能将此lambda编译为定义范围的“命名空间”内的静态函数(gcc肯定似乎)。对于我的生活,我无法弄清楚签名究竟是什么。它看起来肯定有一个应该通过模板专业化(基于错误报告)推导出来。
另一个切线问题是即使我可以使用签名,交叉编译器如何危险这个呢? lambda编译签名是标准化的还是全面的?
答案 0 :(得分:6)
总结并从评论中延伸:
Per [expr.prim.lambda] / 3, lambda-expression 的类型是类类型,就像“普通的,命名的函数对象类型”一样:
lambda-expression 的类型(也是闭包对象的类型)是一个唯一的,未命名的非联合类类型 - 称为闭包类型 [...]
再向下,/ 5指定:
lambda-expression 的闭包类型有一个公共
inline
函数调用操作符(13.5.4),其参数和返回类型由 lambda-expression <描述/ em>分别是参数声明子句和尾随返回类型。当且仅当 lambda-expression 的parameter-declaration-clause后面没有mutable时,才调用此函数调用运算符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...)>;
应该已经能够处理lambdas了。 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-expression 产生函数类型的误解有关。如果我们将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's version更为笼统,可能更好,依赖于SFINAE检查T::operator()
是否存在,如果无法找到运营商,则可以提供更好的诊断信息。
N.B。如果为lambda表达式添加前缀,该表达式不会捕获带有一元+
的任何内容,则它将转换为指向函数的指针。 do_something( +[](int){} )
将使用专业化invokable_type<Return(*)(Args...)>
。
答案 1 :(得分:1)
正如DyP所提到的,lambda仿函数保证有公共operator ()
。请注意,operator ()
不能是static
或非会员。
(由于存在转换运算符到函数指针类型,类型也可以调用,无状态lambda确实有这样的转换运算符,但它们仍然必须提供operator ()
。
您可以使用operator ()
获取decltype( & T::operator() )
的签名,前提是只有一个重载,这是lambdas的保证。这导致指向成员函数类型的指针。您可以使用元函数去除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';
}