在C++ 0x中专门研究lambda上的模板

Ton*_*ato 25 c++ lambda templates specialization c++11

我编写了一个traits类,它允许我在C++ 0x中提取有关函数或函数对象的参数和类型的信息(使用gcc 4.5.0测试).一般情况处理函数对象:

template <typename F>
struct function_traits {
    template <typename R, typename... A>
    struct _internal { };

    template <typename R, typename... A>
    struct _internal<R (F::*)(A...)> {
        // ...
    };

    typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>;
};
Run Code Online (Sandbox Code Playgroud)

然后我对全局范围内的普通函数进行了专门化:

template <typename R, typename... A>
struct function_traits<R (*)(A...)> {
    // ...
};
Run Code Online (Sandbox Code Playgroud)

这工作正常,我可以将一个函数传递给模板或函数对象,它可以正常工作:

template <typename F>
void foo(F f) {
    typename function_traits<F>::whatever ...;
}

int f(int x) { ... }
foo(f);
Run Code Online (Sandbox Code Playgroud)

如果foo我不想传递一个函数或函数对象,而是想传递一个lambda表达式,该怎么办?

foo([](int x) { ... });
Run Code Online (Sandbox Code Playgroud)

这里的问题是既不function_traits<>适用专业化.C++ 0x草案表明表达式的类型是"唯一的,未命名的,非联合类类型".解析调用typeid(...).name()表达式的结果给了我看似gcc的lambda内部命名约定main::{lambda(int)#1},而不是语法上代表C++类型名称的东西.

简而言之,我可以在这里放入模板:

template <typename R, typename... A>
struct function_traits<????> { ... }
Run Code Online (Sandbox Code Playgroud)

这将允许此traits类接受lambda表达式?

Sum*_*ant 18

我认为可以专门化lambdas的特征,并对未命名的仿函数的签名进行模式匹配.这是适用于g ++ 4.5的代码.尽管它有效,但lambda上的模式匹配似乎与直觉相反.我有内联评论.

struct X
{
  float operator () (float i) { return i*2; }
  // If the following is enabled, program fails to compile
  // mostly because of ambiguity reasons.
  //double operator () (float i, double d) { return d*f; } 
};

template <typename T>
struct function_traits // matches when T=X or T=lambda
// As expected, lambda creates a "unique, unnamed, non-union class type" 
// so it matches here
{
  // Here is what you are looking for. The type of the member operator()
  // of the lambda is taken and mapped again on function_traits.
  typedef typename function_traits<decltype(&T::operator())>::return_type return_type;
};

// matches for X::operator() but not of lambda::operator()
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...)> 
{
  typedef R return_type;
};

// I initially thought the above defined member function specialization of 
// the trait will match lambdas::operator() because a lambda is a functor.
// It does not, however. Instead, it matches the one below.
// I wonder why? implementation defined?
template <typename R, typename... A>
struct function_traits<R (*)(A...)> // matches for lambda::operator() 
{
  typedef R return_type;
};

template <typename F>
typename function_traits<F>::return_type
foo(F f)
{
  return f(10);
}

template <typename F>
typename function_traits<F>::return_type
bar(F f)
{
  return f(5.0f, 100, 0.34);
}

int f(int x) { return x + x;  }

int main(void)
{
  foo(f);
  foo(X());
  bar([](float f, int l, double d){ return f+l+d; });
}
Run Code Online (Sandbox Code Playgroud)

  • *(我意外地删除了我的评论,这里又一次):*lambda(捕获或非捕获)的`decltype(&T :: operator())`将匹配`template struct function_traits <R(C: :*)(A ...)const>`但不是`template struct function_traits <R(C ::*)(A ...)>`.注意结尾附近的`const`.基本上,不要忘记`*this`上的cv-qualifiers.有关更多信息,请参阅http://stackoverflow.com/questions/25654186/trait-to-drop-const-from-a-member-function-type (3认同)
  • 我认为它背后的推理可能是在这种情况下"C"将是一个实现定义的类型,并且通过模板参数提供对它的访问将允许我们做无意义的事情,比如为它定义typedef别名.因为我们不能对lambda类型做任何事情,除了像函数一样调用它之外,从语言设计的角度来看更有意义强迫它成为适用的特化. (2认同)

Aar*_*aid 6

void_t技巧可以帮助。`void_t` 是如何工作的

除非你有 C++17,否则你需要包含以下定义void_t

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
Run Code Online (Sandbox Code Playgroud)

向原始模板添加一个额外的模板参数,默认为void

template <typename T, typename = void>
struct function_traits;
Run Code Online (Sandbox Code Playgroud)

简单函数的 traits 对象与您已有的相同:

template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};
Run Code Online (Sandbox Code Playgroud)

对于非常量方法:

template <typename R, typename... A>
struct function_traits<R (C::*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};
Run Code Online (Sandbox Code Playgroud)

不要忘记const方法:

template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...) const> // const
{
    using return_type = R;
    using class_type  = C;
    using args_type   = std:: tuple< A... >;
};
Run Code Online (Sandbox Code Playgroud)

最后,重要的特质。给定一个类类型,包括 lambda 类型,我们希望从Tto转发decltype(&T::operator())。我们要确保这个特点是仅适用于类型T为它::operator()是可用的,这就是void_t我们做。为了强制执行这个约束,我们需要在&T::operator()某处放入特征签名,因此template <typename T> struct function_traits<T, void_t< decltype(&T::operator())

template <typename T>
struct   function_traits<T, void_t< decltype(&T::operator()) > > 
: public function_traits<           decltype(&T::operator())   >
{
};
Run Code Online (Sandbox Code Playgroud)

(non- mutable, non-generic) lambdas 中的 operator() 方法是const,这解释了为什么我们需要const上面的模板。

但最终这是非常严格的。这不适用于通用 lambda 表达式或带有 templated 的对象operator()。如果您重新考虑您的设计,您会发现一种更灵活的不同方法。