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

mr.*_*bbe 3 c++ lambda gcc metaprogramming c++11

虽然有很多东西浮出水面来获取任何模板化回调函数/方法的返回类型(当然包括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;
   };
};
Run Code Online (Sandbox Code Playgroud)

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)...);
}
Run Code Online (Sandbox Code Playgroud)

没关系是什么do_something_handler......它完全不重要.问题在于lambda函数.

对于我专门用于的所有可能的通用可调用签名(看起来只是非STL仿函数),当do_something()它们作为第一个参数(模板推导完全有效)调用时,这种方法非常有效.但是,lambda函数会导致未捕获的类型签名,从而导致invokable_type<Sig>被使用,这意味着类似于::n并且::args<0>::type根本不存在的东西.

不是问题的例子......

void something(int x, int y) {
    return x * y;
}
Run Code Online (Sandbox Code Playgroud)

... 然后...

do_something(something, 7, 23);
Run Code Online (Sandbox Code Playgroud)

问题例子......

do_something([](int x, int y) {
        return x * y;
    }, 7, 23);
Run Code Online (Sandbox Code Playgroud)

如果我正确理解lambda函数,编译器很可能将此lambda编译为定义范围的"命名空间"内的静态函数(gcc当然可以).对于我的生活,我无法弄清楚签名究竟是什么.它看起来肯定有一个应该通过模板特化(基于错误报告)可以推导出来.

另一个切线问题是,即使有一个我可以使用的签名,交叉编译器如何危险这个呢?lambda编译签名是标准化的还是全面的?

dyp*_*dyp 6

总结并从评论中扩展:

Per [expr.prim.lambda]/3,lambda-expression的类型是类类型,就像"普通的,命名的函数对象类型"一样:

的类型的λ-表达(这也是封闭的对象的类型)是一个独特的,无名不愈合类类型-称为闭合类型 [...]

再往下,/ 5指定:

lambda表达式的闭包类型有一个公共inline函数调用操作符(13.5.4),其参数和返回类型分别由lambda-expressionparameter-declaration-clausetrailing-return-type描述.const当且仅当lambda-expression的parameter-declaration-clause后面没有mutable 时,才声明此函数调用操作符(9.3.1).它既不是虚拟也不是声明 volatile.[...]

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

这意味着lambda [](int p){ return p/2.0; }在这方面表现得很像

struct named_function_object
{
    double operator() (int p) const { return p/2.0; }
};
Run Code Online (Sandbox Code Playgroud)

因此,你的第一次专业化

template<typename R, typename...As>
struct invokable_type<R(As...)>;
Run Code Online (Sandbox Code Playgroud)

应该已经能够处理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;
}
Run Code Online (Sandbox Code Playgroud)

在最新版本的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;
}
Run Code Online (Sandbox Code Playgroud)

然后对于类似的调用do_something( [](int){} ),模板参数C将被推导为闭包类型(无引用),即类类型.struct test上面定义的类似情况将是do_something( test{} ),在这种情况下C将被推导出来test.

invokable_type因此,实例化的专业化是一般情况

template<class T>
struct invokable_type;
Run Code Online (Sandbox Code Playgroud)

因为T在两种情况下都不是像指针或函数类型那样的"复合类型".这种一般情况可以通过假设它只采用纯类类型,然后使用T::operator()该类类型的成员来使用:

template<class T>
struct invokable_type
{
    constexpr static int n = invokable_type<&T::operator()>::n;
};
Run Code Online (Sandbox Code Playgroud)

或者,正如Potatoswatter所说,通过继承

template<class T>
struct invokable_type
    : invokable_type<&T::operator()>
{};
Run Code Online (Sandbox Code Playgroud)

然而,Potatoswatter的版本更为通用,可能更好,依靠SFINAE检查是否存在T::operator(),如果无法找到操作员,可以提供更好的诊断信息.

注意如果为一个lambda表达式添加前缀,该表达式不会捕获带有一元的任何内容+,它将被转换为指向函数的指针.do_something( +[](int){} )将与专业化合作invokable_type<Return(*)(Args...)>.