27

我编写了一个特征类,可以让我提取有关 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<>适用。C++0x 草案说表达式的类型是“唯一的、未命名的、非联合类类型”。拆解调用typeid(...).name()表达式的结果给了我似乎是 gcc 对 lambda 的内部命名约定main::{lambda(int)#1},而不是在语法上表示 C++ 类型名的东西。

简而言之,我可以在这里放入模板中的任何内容:

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

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

4

3 回答 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; });
}
于 2010-04-02T04:46:40.620 回答
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;

向原始模板添加一个额外的模板参数,默认为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 类型,我们希望从 toT转发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())   >
{
};

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

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

于 2016-09-27T06:12:52.150 回答
1

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

首先,我应该说相关方法是一种const方法,用于 lambda(用于非捕获、非泛型、非mutablelambda)。所以你将无法区分真正的 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>() ) )
{
};
于 2016-09-22T18:51:13.220 回答