是否可以确定 lambda 的参数类型和返回类型?

Posted

技术标签:

【中文标题】是否可以确定 lambda 的参数类型和返回类型?【英文标题】:Is it possible to figure out the parameter type and return type of a lambda? 【发布时间】:2011-12-18 02:46:25 【问题描述】:

给定一个 lambda,是否可以确定它的参数类型和返回类型?如果是,怎么做?

基本上,我想要lambda_traits,可以通过以下方式使用:

auto lambda = [](int i)  return long(i*10); ;

lambda_traits<decltype(lambda)>::param_type  i; //i should be int
lambda_traits<decltype(lambda)>::return_type l; //l should be long

背后的动机是我想在一个接受 lambda 作为参数的函数模板中使用lambda_traits,我需要知道它在函数内部的参数类型和返回类型:

template<typename TLambda>
void f(TLambda lambda)

   typedef typename lambda_traits<TLambda>::param_type  P;
   typedef typename lambda_traits<TLambda>::return_type R;

   std::function<R(P)> fun = lambda; //I want to do this!
   //...

暂时,我们可以假设 lambda 只接受一个参数。

最初,我尝试使用 std::function 作为:

template<typename T>
A<T> f(std::function<bool(T)> fun)

   return A<T>(fun);


f([](int)return true;); //error

但这显然会出错。于是我改成TLambda版本的函数模板,想在函数内部构造std::function对象(如上图)。

【问题讨论】:

如果你知道参数类型,那么this可以用来确定返回类型。我不知道如何弄清楚参数类型。 是否假定函数采用单个参数? "参数类型" 但是任意 lambda 函数没有参数类型。它可以采用任意数量的参数。因此,任何特征类都必须设计为按位置索引查询参数。 @iammilind:是的。目前,我们可以假设。 @NicolBolas:暂时,我们可以假设 lambda 只接受一个参数。 【参考方案1】:

有趣的是,我刚刚基于 Specializing a template on a lambda in C++0x 写了一个function_traits implementation,它可以给出参数类型。如该问题的答案所述,诀窍是使用 lambda 的 operator()decltype

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
;
// For generic types, directly use the result of the signature of its 'operator()'

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function

    enum  arity = sizeof...(Args) ;
    // arity is the number of arguments.

    typedef ReturnType result_type;

    template <size_t i>
    struct arg
    
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
        // the i-th argument is equivalent to the i-th tuple element of a tuple
        // composed of those arguments.
    ;
;

// test code below:
int main()

    auto lambda = [](int i)  return long(i*10); ;

    typedef function_traits<decltype(lambda)> traits;

    static_assert(std::is_same<long, traits::result_type>::value, "err");
    static_assert(std::is_same<int, traits::arg<0>::type>::value, "err");

    return 0;

请注意,此解决方案适用于像 [](auto x) 这样的通用 lambda。

【讨论】:

嘿,我只是在写这个。没想到tuple_element,谢谢。 @GMan:如果您的方法与此不完全相同,请发布。我要测试这个解决方案。 一个完整的 trait 也将使用非 const 的特化,对于那些声明为 mutable ([]() mutable -&gt; T ... ) 的 lambda。 @Andry 对于具有(可能)多个operator() 重载的函数对象来说,这是一个根本问题,而不是这个实现。 auto 不是类型,所以它永远不可能是 traits::template arg&lt;0&gt;::type 的答案 @helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/… 作为断开链接的解决方案:尝试从根目录搜索,Luke。【参考方案2】:

虽然我不确定这是否严格符合标准, ideone编译如下代码:

template< class > struct mem_type;

template< class C, class T > struct mem_type< T C::* > 
  typedef T type;
;

template< class T > struct lambda_func_type 
  typedef typename mem_type< decltype( &T::operator() ) >::type type;
;

int main() 
  auto l = [](int i)  return long(i); ;
  typedef lambda_func_type< decltype(l) >::type T;
  static_assert( std::is_same< T, long( int )const >::value, "" );

但是,这仅提供函数类型,因此结果和参数 必须从中提取类型。 如果您可以使用boost::function_traitsresult_typearg1_type 会达到目的。 由于 ideone 似乎没有在 C++11 模式下提供提升,所以我无法发布 实际代码,抱歉。

【讨论】:

我认为,这是一个好的开始。为此+1。现在我们需要处理函数类型以提取所需的信息。 (我现在不想使用 Boost,因为我想学习这些东西)。【参考方案3】:

@KennyTMs 答案中显示的专业化方法可以扩展为涵盖所有情况,包括可变参数和可变 lambda:

template <typename T>
struct closure_traits : closure_traits<decltype(&T::operator())> ;

#define REM_CTOR(...) __VA_ARGS__
#define SPEC(cv, var, is_var)                                              \
template <typename C, typename R, typename... Args>                        \
struct closure_traits<R (C::*) (Args... REM_CTOR var) cv>                  \
                                                                          \
    using arity = std::integral_constant<std::size_t, sizeof...(Args) >;   \
    using is_variadic = std::integral_constant<bool, is_var>;              \
    using is_const    = std::is_const<int cv>;                             \
                                                                           \
    using result_type = R;                                                 \
                                                                           \
    template <std::size_t i>                                               \
    using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \
;

SPEC(const, (,...), 1)
SPEC(const, (), 0)
SPEC(, (,...), 1)
SPEC(, (), 0)

Demo.

请注意,没有为可变参数 operator()s 调整元数。相反,也可以考虑is_variadic

【讨论】:

【参考方案4】:

@KennyTMs 提供的答案效果很好,但是如果 lambda 没有参数,则使用索引 arg 不会编译。如果其他人遇到这个问题,我有一个简单的解决方案(比使用 SFINAE 相关的解决方案更简单,也就是说)。

只需在可变参数类型之后的 arg 结构中的元组末尾添加 void。即

template <size_t i>
    struct arg
    
        typedef typename std::tuple_element<i, std::tuple<Args...,void>>::type type;
    ;

由于 arity 不依赖于模板参数的实际数量,实际不会不正确,如果它为 0,那么至少 arg 仍然存在,您可以随心所欲地使用它。如果您已经计划不超过索引arg&lt;arity-1&gt;,那么它不应该干扰您当前的实现。

【讨论】:

以上是关于是否可以确定 lambda 的参数类型和返回类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过一系列 lambdas 传递数据,其中参数类型是根据前一个 lambda 的返回类型推断的?

C++Lambda表达式作为参数

C++Lambda表达式作为参数

C++Lambda表达式作为参数

find_if 中具有多个参数的 Lambda

Kotlin函数 ⑧ ( 函数引用 作为函数参数 | ::函数名 | 函数类型 作为函数返回值类型 )