我编写了一个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>>;
};
然后我对全局范围内的普通函数进行了专门化:
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<????> { ... }
允许此traits类接受lambda表达式吗?
答案 0 :(得分: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; });
}
答案 1 :(得分: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;
简单函数的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 (*)(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(&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
,非泛型)lambdas中的operator()方法为const
,这解释了为什么我们需要上面的const
模板。
但最终这是非常严格的。这不适用于通用lambda或带有模板化operator()
的对象。如果您重新考虑您的设计,您会发现找到一种更灵活的不同方法。
答案 2 :(得分:1)
通过将部分工作委托给一系列功能模板而不是类模板,您可以提取相关信息。
首先,我应该说相关方法是const
方法,对于lambda(对于非捕获,非泛型,非 - mutable
lambda)。因此,您无法区分真正的lambda与此之间的区别:
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>() ) )
{
};