使用可变参数模板参数来解析lambda签名

虽然有很多关于获取任何模板化回调函数/方法(包括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...) :))),存在特化(和其他属性/ typedefs) (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()时,它会很好地工作。 但是,lambda函数导致未捕获的类型签名,导致使用invokable_type 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编译签名是标准化还是全面?


总结和扩展评论:

Per [expr.prim.lambda] / 3,lambda表达式的类型是类类型,就像“普通的,命名的函数对象类型”一样:

lambda表达式(也是闭包对象的类型)的类型是唯一的,未命名的非联合类类型 - 称为闭包类型[...]

再往下,/ 5指定:

lambda表达式的闭包类型有一个公共inline函数调用操作符(13.5.4),其参数和返回类型分别由lambda表达式的parameter-declaration-clause和trailing-return-type来描述。 当且仅当lambda表达式的参数声明子句没有跟随可变时,此函数调用运算符被声明为const (9.3.1)。 这既不是虚拟的,也没有宣布volatile 。 [...]

(然后通过指定属性和异常规范继续)

这意味着lambda [](int p){ return p/2.0; } [](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或非成员。

(由于转换运算符存在函数指针类型,因此类型也可以被调用,而无状态lambdas确实有这样的转换运算符,但它们仍然必须提供operator ()

您可以使用decltype( & T::operator() )获得operator ()的签名,前提是只有一个重载,这对lambdas是有保证的。 这会产生一个指向成员函数类型的指针。 您可以使用元函数去掉T:: part,或者直接写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

链接地址: http://www.djcxy.com/p/51575.html

上一篇: Using variadic template arguments to resolve a lambda signature

下一篇: How can I iterate over a packed variadic template argument list?