3

虽然有很多关于获取任何模板化回调函数/方法(当然包括 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 内置了一个,但我从不费心去看)。这个问题不重要。

然后,当然,对于无数可调用类型(例如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 编译签名是标准化的还是全面的?

4

2 回答 2

6

总结和扩展评论:

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

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

再往下, /5 指定:

lambda-expression的闭包类型有一个公共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; }
};

因此,你的第一个专业

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 表达式会产生函数类型的误解有关。如果我们定义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 表达式加上 unary 前缀+,它将被转换为一个指向函数的指针。do_something( +[](int){} )将与专业合作invokable_type<Return(*)(Args...)>

于 2013-10-03T01:48:52.897 回答
1

正如 DyP 提到的,lambda 仿函数保证有一个 public operator ()。请注意,operator ()不能是static或非会员。

(由于存在到函数指针类型的转换运算符,一个类型也可以被调用,并且无状态 lambda 确实有这样的转换运算符,但它们仍然必须提供operator ().

您可以获得operator ()using的签名decltype( & T::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

于 2013-10-03T02:19:30.063 回答