能否确定lambda的参数类型和返回类型?

147

给定一个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对象(如上所示)。


如果您知道参数类型,那么可以使用这个来确定返回类型。但我不知道如何确定参数类型。 - Mankarse
函数只接受一个参数吗? - iammilind
1
"参数类型",但是任意的 lambda 函数都没有参数类型。它可以接受任意数量的参数。因此,任何特性类都必须被设计为通过位置索引查询参数。 - Nicol Bolas
@iammilind:是的,暂时我们可以这样假设。 - Nawaz
@NicolBolas:目前,我们可以假设lambda表达式只接受一个参数。 - Nawaz
显示剩余2条评论
5个回答

181
有趣的是,我刚写了一个基于 C++0x中模板上lambda特化function_traits 实现,可以给出参数类型。就像那个问题的答案中描述的那样,技巧是使用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表达式。


3
完整的特化应该也会针对非const使用一种专门的方式,用于那些声明为mutable的 lambda 表达式(` mutable -> T { ... })。 - Luc Danton
先尝试使用function_traits特化,然后再#include <random>。否则编译器会疯掉的。 - cdiggins
1
@Andry,这是函数对象的一个基本问题,因为它们可能有多个operator()重载,而不是这个实现。auto不是一种类型,因此它永远不能成为traits::template arg<0>::type的答案。 - Caleth
如果有人对更好的实现感兴趣:https://svn.code.sf.net/p/tacklelib/tacklelib/trunk/utility/type_traits.hpp - Andry
1
作为损坏链接的解决方案:尝试从根目录搜索,Luke。 - Andry
显示剩余14条评论

12

虽然我不确定这是否严格符合标准, 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_traits,则result_typearg1_type将满足目的。 由于ideone似乎在C++11模式下不提供boost,我无法发布实际代码,抱歉。


1
我认为这是一个不错的开始。对此给予加1。现在我们需要处理函数类型以提取所需信息。(我暂时不想使用Boost,因为我想学习这些东西)。 - Nawaz

9

@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)

演示

请注意,对于可变参数的operator(),其元数不会进行调整。而可以考虑使用is_variadic


2

@KennyTMs提供的答案非常好,但是如果lambda没有参数,则使用索引arg<0>会导致编译错误。如果其他人也遇到了这个问题,我有一个简单的解决方案(比使用SFINAE相关解决方案更简单)。

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

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

由于元数不取决于实际模板参数的数量,因此实际情况不会不正确,如果它为0,则至少arg<0>仍将存在,您可以对其进行任何操作。如果您已经计划不超过索引arg<arity-1>,则它不应干扰您当前的实现。


0
如果你正在寻找一个可以调用的C++中所有类型的完整解决方案,很多答案都能起作用,但是会忽略一些特殊情况,比如:
  • 对lambda的引用
  • 函数和函数指针

这是我所知道的一个完整解决方案(除了通用lambda)- 如果有任何遗漏,请在评论中告诉我:

template <typename>
struct closure_traits;

template <typename FunctionT> // overloaded operator () (e.g. std::function)
struct closure_traits
    : closure_traits<decltype(&std::remove_reference_t<FunctionT>::operator())>
{
};

template <typename ReturnTypeT, typename... Args> // Free functions
struct closure_traits<ReturnTypeT(Args...)>
{
    using arguments = std::tuple<Args...>;

    static constexpr std::size_t arity = std::tuple_size<arguments>::value;

    template <std::size_t N>
    using argument_type = typename std::tuple_element<N, arguments>::type;

    using return_type = ReturnTypeT;
};

template <typename ReturnTypeT, typename... Args> // Function pointers
struct closure_traits<ReturnTypeT (*)(Args...)>
    : closure_traits<ReturnTypeT(Args...)>
{
};

// member functions
template <typename ReturnTypeT, typename ClassTypeT, typename... Args>
struct closure_traits<ReturnTypeT (ClassTypeT::*)(Args...)>
    : closure_traits<ReturnTypeT(Args...)>
{
    using class_type = ClassTypeT;
};

// const member functions (and lambda's operator() gets redirected here)
template <typename ReturnTypeT, typename ClassTypeT, typename... Args>
struct closure_traits<ReturnTypeT (ClassTypeT::*)(Args...) const>
    : closure_traits<ReturnTypeT (ClassTypeT::*)(Args...)>
{
};

免责声明: std::remove_reference 的灵感来自于 this code


1
涵盖了大多数主流场景,但不是完整的解决方案(请参见我自己的生产级、完全记录的解决方案:https://github.com/HexadigmAdmin/FunctionTraits)。您缺少以下特化处理:处理noexcept(自C++17以来函数类型的一部分-这是必须的),非静态成员函数上的“volatile”cv限定符(虽然在现实世界中非常罕见),成员函数上的ref限定符(同样非常罕见),可变参数函数(带有“…”参数)和调用约定(只会检测默认值-通常为“cdecl”)。 - Larry
此外,带有cv限定符的函数指针将无法被检测到(调用者必须首先调用“std::remove_cv_t” - 这不是一个致命问题,但有点不方便)。 - Larry

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