在C++0x中,将一个模板专门针对一个lambda进行处理

27

我写了一个特性类(traits class),可以让我提取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>>;
};

然后我有一个专门用于全局作用域下普通函数的特化:

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

这个很好用,我可以将一个函数传递到模板中或者一个函数对象,它可以正常工作:

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

int f(int x) { ... }
foo(f);

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

foo([](int x) { ... });
这里的问题在于function_traits<>的任何一个特化都不适用。C++0x草案规定表达式的类型是“独特的、无名的、非联合类类型”。调用typeid(...).name()返回的结果反解后,给出了似乎是gcc内部命名约定用于lambda表达式的字符串main::{lambda(int)#1},它并不能表示一个C++类型名称。
简而言之,在这里我能放入什么东西到模板中呢?
template <typename R, typename... A>
struct function_traits<????> { ... }

如何让该特质类接受一个lambda表达式?


不。你为什么认为你需要这样的东西? - sellibitze
2
我认为我的例子提供了一个不错的用例:如果我有一个通用算法,它接受一个函数或函数对象,我可以使用这个特性类来确定不仅返回类型(现在也可以使用decltype来完成),还有参数的类型。(我省略了大部分代码,以免帖子过长。)由于我可以传递一个函数或函数对象,出于正交性的目的,我也想能够传递一个lambda。这基本上是从阅读《程序设计元素》中产生的学术练习。 - Tony Allevato
基本上,我对函数结果的类型以及其参数的类型/数量感兴趣;上面模板中表示为R和A的东西。我刚刚再次尝试了一下,形式为[](int x) { return x; }的lambda可以明确地转换为常规函数指针int(*)(int),因此似乎应该有一种方法将其用于我的优势。 我只需要使它与function_traits的另外两个版本一起工作,特别是那个不在其参数上进行专门化的函数对象的版本。 - Tony Allevato
@Tony:仔细想了想,我认为你想要的不可能实现。我会再考虑一下,但是我撤回它可能性的说法。 :) - GManNickG
请注意,由于在operator()上进行重载和/或使用成员模板,函数对象可能没有单一正确的“类型”。我甚至会说,依赖只有一个非模板化函数调用运算符的函数对象类型通常是一个不好的主意。 - sellibitze
显示剩余2条评论
3个回答

20

我认为可以为lambda专门化特征,并在未命名的函数对象的签名上进行模式匹配。以下是在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; });
}

很高兴它有所帮助。虽然获取lambda的operator()语法类似于类成员函数,但lambda毕竟是匿名独立函数,与R ()(A...)匹配而不是R (C::)(A...)。 - Sumant
2
我认为背后的原因可能是,在这种情况下,“C”将是一个实现定义的类型,通过模板参数提供对它的访问将允许我们做一些荒谬的事情,比如为它定义typedef别名。由于我们除了像函数一样调用它之外不能对lambda类型做任何事情,从语言设计的角度来看,强制它成为适用的特化更有意义。 - Tony Allevato
尝试在[]中使用捕获变量,因为(n3333 std draft) 5.1.2 Lambda表达式 [expr.prim.lambda] "6.没有lambda-capture的lambda表达式的闭包类型具有一个公共的非虚拟的非显式const转换函数,以指向具有与闭包类型的函数调用运算符相同的参数和返回类型的函数指针。通过这个转换函数返回的值应该是一个函数的地址,当被调用时,具有与调用闭包类型的函数调用运算符相同的效果." - idupree
3
lambda表达式(包括捕获和非捕获形式)的 decltype(&T::operator()) 会匹配模板结构体 function_traits<R (C::*)(A...) const>,但不会匹配模板结构体 function_traits<R (C::*)(A...)>。注意 const 关键字出现在最后面。基本上,不要忘记在 *this 上添加cv限定符。更多信息请参见https://dev59.com/2oPba4cB1Zd3GeqPnROR - Aaron McDaid
我该如何在一个接收lambda的函数模板中使用这些traits,例如< typename T > void func(T lambda){},以声明一个std::function<RetType(Args...)>? - barney
显示剩余3条评论

7
void_t技巧可以帮助解决问题。如果您没有C++17,您需要包含void_t的定义。具体了解void_t的工作原理可参考:`void_t`是如何工作的?
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

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

template <typename T, typename = void>
struct function_traits;

简单函数的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... >;
};

对于非const方法:

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... >;
};

别忘了 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... >;
};

最后,重要的特性。给定一个类类型,包括lambda类型,我们希望从Tdecltype(&T::operator())转发。我们希望确保这个特性只适用于::operator()可用的类型T,这就是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())   >
{
};
< p >(非 mutable ,非通用)lambda中的operator()方法是const,这就解释了为什么我们需要上面的const模板。

但最终这是非常限制性的。这不适用于通用lambda或具有模板化 operator()的对象。如果您重新考虑设计,您会找到一个更灵活的不同方法。


1
通过将一些工作委托给一系列函数模板而不是类模板,您可以提取相关信息。
首先,我应该说,相关方法是一个const方法,用于lambda(对于非捕获,非通用,非可变lambda)。因此,您将无法区分真正的lambda和这个lambda之间的区别:
struct {
    int operator() (int) const { return 7; }
} object_of_unnamed_name_and_with_suitable_method;

因此,我必须假设您不想为lambda函数提供“特殊处理”,也不想测试类型是否是lambda类型,而是希望仅提取任何足够简单的对象的返回类型和所有参数的类型。所谓“足够简单”,我的意思是,例如,operator()方法本身不是模板。并且,作为额外信息,还要有一个布尔值告诉我们是否存在并使用了operator()方法,而不是一个普通的旧函数。

// First, a convenient struct in which to store all the results:
template<bool is_method_, bool is_const_method_, typename C, typename R, typename ...Args>
struct function_traits_results {
    constexpr static bool is_method = is_method_;
    constexpr static bool is_const_method = is_const_method_;
    typedef C class_type; // void for plain functions. Otherwise,
                          // the functor/lambda type
    typedef R return_type;
    typedef tuple<Args...> args_type_as_tuple;
};

// This will extract all the details from a method-signature:
template<typename>
struct intermediate_step;
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...)>  // non-const methods
    : public function_traits_results<true, false, C, R, Args...>
{
};
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...) const> // const methods
    : public function_traits_results<true, true, C, R, Args...>
{
};


// These next two overloads do the initial task of separating
// plain function pointers for functors with ::operator()
template<typename R, typename ...Args>
function_traits_results<false, false, void, R, Args...>
function_traits_helper(R (*) (Args...) );
template<typename F, typename ..., typename MemberType = decltype(&F::operator()) >
intermediate_step<MemberType>
function_traits_helper(F);


// Finally, the actual `function_traits` struct, that delegates
// everything to the helper
template <typename T>
struct function_traits : public decltype(function_traits_helper( declval<T>() ) )
{
};

我该如何在一个接收lambda的函数模板中使用这些traits,例如< typename T > void func(T lambda){},以声明一个std::function<RetType(Args...)>? - barney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接