【问题标题】:Specializing a template on a lambda in C++0x在 C++0x 中专门针对 lambda 模板
【发布时间】:2011-02-03 11:24:00
【问题描述】:

我编写了一个特征类,可以让我提取有关 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);

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

foo([](int x) { ... });

这里的问题是function_traits&lt;&gt; 的特化都不适用。 C++0x 草案说表达式的类型是“唯一的、未命名的、非联合类类型”。对表达式调用 typeid(...).name() 的结果进行拆解,我得到了 gcc 对 lambda 的内部命名约定 main::{lambda(int)#1},而不是语法上代表 C++ 类型名的东西。

简而言之,有什么我可以在这里放入模板的吗:

template <typename R, typename... A>
struct function_traits<????> { ... }

这将允许该特征类接受 lambda 表达式?

【问题讨论】:

  • 没有。为什么你认为你需要这样的东西?
  • 我认为我的示例给出了一个不错的用例:如果我有一个接受函数或函数对象的通用算法,我可以使用这个特征类来确定返回类型(也可以现在用 decltype 完成),还有参数的类型。 (我省略了大部分代码以防止帖子过长。)因为我可以传入一个函数或函数对象,出于正交性的目的,我也希望能够传入一个 lambda。这基本上都是一个学术练习,源于阅读“编程元素”。
  • @Tony:答案是肯定的,我已经做到了。不过,我稍后会回到这个问题。你想获得什么特质?
  • 我应该澄清一下:是的,这取决于你想要什么。
  • 基本上我对函数结果的类型及其参数的类型/数量感兴趣;上面的模板中由 R 和 A... 表示的东西。我只是玩弄了一些其他形式的 lambda [](int x) { return x; } 可以显式地转换为常规函数指针 int(*)(int),所以似乎应该有一种方法可以利用它对我有利。我只需要将其与其他两个版本的 function_traits 一起工作,尤其是用于不专门处理其参数的函数对象的版本。

标签: c++ templates lambda c++11 specialization


【解决方案1】:

我认为可以为 lambda 专门化特征并在未命名函子的签名上进行模式匹配。这是适用于 g++ 4.5 的代码。虽然它有效,但 lambda 上的模式匹配似乎与直觉相反。我已经内联了 cmets。

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...).
  • 我想它背后的原因可能是在这种情况下“C”将是一个实现定义的类型,并且通过模板参数提供对它的访问将允许我们做一些无意义的事情,比如定义 typedef它的别名。由于我们不能对 lambda 类型做任何事情,只能将其称为函数,因此从语言设计的角度来看,强制它成为适用的专业化更有意义。
  • 也尝试在 [] 中使用捕获的变量,因为 (n3333 std Draft) 第 5.1.2 节 Lambda 表达式 [expr.prim.lambda] "6. lambda 表达式的闭包类型没有 lambda-capture 有一个公共的非虚拟非显式 const 转换函数指向函数的指针,该函数具有与闭包类型的函数调用运算符相同的参数和返回类型。此转换函数返回的值应为函数的地址调用时,它与调用闭包类型的函数调用运算符具有相同的效果。”
  • (我不小心删除了我的评论,又来了): lambda(捕获或非捕获)的 decltype(&amp;T::operator()) 将匹配 template struct function_traits&lt;R (C::*)(A...) const&gt; 但不是template struct function_traits&lt;R (C::*)(A...)&gt;。注意结尾处的const。基本上,不要忘记*this 上的 cv-qualifiers。有关更多信息,请参阅stackoverflow.com/questions/25654186/…
【解决方案2】:

void_t 技巧可以提供帮助。 How does `void_t` work?

除非您有 C++17,否则您需要包含 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;

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

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

对于非常量方法:

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 类型,我们希望从 T 转发到 decltype(&amp;T::operator())。我们希望确保此 trait 仅适用于 ::operator() 可用的类型 T,这就是 void_t 为我们所做的。为了强制执行此约束,我们需要将&amp;T::operator() 放入特征签名的某处,因此template &lt;typename T&gt; struct function_traits&lt;T, void_t&lt; decltype(&amp;T::operator())

template <typename T>
struct   function_traits<T, void_t< decltype(&T::operator()) > > 
: public function_traits<           decltype(&T::operator())   >
{
};

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

但最终这是非常严格的。这不适用于通用 lambda,或模板化 operator() 的对象。如果您重新考虑您的设计,您会发现另一种更灵活的方法。

【讨论】:

    【解决方案3】:

    通过将一些工作委托给一系列函数模板而不是类模板,您可以提取相关信息。

    首先,我应该说相关方法是const 方法,用于 lambda(用于非捕获、非泛型、非mutable lambda)。所以你将无法区分真正的 lambda 和 this:

    struct {
        int operator() (int) const { return 7; }
    } object_of_unnamed_name_and_with_suitable_method;
    

    因此,我必须假设您不想对 lambdas 进行“特殊处理”,并且您不想测试类型 是否 是 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>() ) )
    {
    };
    

    【讨论】:

    • 我如何在函数模板中使用这个特征,它接收像 void func(T lambda){} 这样的 lambda 来声明 std::function 在里面?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-21
    相关资源
    最近更新 更多