web-dev-qa-db-fra.com

Est-il possible de comprendre le type de paramètre et le type de retour d'un lambda?

Étant donné un lambda, est-il possible de comprendre son type de paramètre et son type de retour? Si oui, comment?

Fondamentalement, je veux lambda_traits qui peut être utilisé des manières suivantes:

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

La motivation derrière est que je veux utiliser lambda_traits dans un modèle de fonction qui accepte un lambda comme argument, et j'ai besoin de connaître son type de paramètre et son type de retour dans la fonction:

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!
   //...
}

Pour le moment, nous pouvons supposer que le lambda prend exactement un argument.

Au départ, j'ai essayé de travailler avec std::function comme:

template<typename T>
A<T> f(std::function<bool(T)> fun)
{
   return A<T>(fun);
}

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

Mais cela donnerait évidemment une erreur. Je l'ai donc changé en TLambda version du modèle de fonction et je veux construire le std::function objet à l'intérieur de la fonction (comme illustré ci-dessus).

128
Nawaz

Drôle, je viens d'écrire une function_traits Implémentation basée sur Spécialisation d'un modèle sur un lambda en C++ 0x qui peut donner les types de paramètres. L'astuce, comme décrit dans la réponse à cette question, consiste à utiliser le decltype de la operator() du lambda.

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;
}

Notez que cette solution ne fonctionne pas pour lambda générique comme [](auto x) {}.

152
kennytm

Bien que je ne sois pas sûr que ce soit strictement conforme aux normes, ideone a compilé le code suivant:

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, "" );
}

Cependant, cela ne fournit que le type de fonction, donc les types de résultat et de paramètre doivent en être extraits. Si vous pouvez utiliser boost::function_traits, result_type et arg1_type répondra à l'objectif. Comme ideone ne semble pas fournir de boost en mode C++ 11, je n'ai pas pu poster le code réel, désolé.

11
Ise Wisteria

La méthode de spécialisation indiquée dans la réponse de @KennyTM peut être étendue pour couvrir tous les cas, y compris les lambdas variadiques et mutables:

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)

Démo .

Notez que l'arité n'est pas ajustée pour variadic operator() s. Au lieu de cela, on peut également considérer is_variadic.

6
Columbo

La réponse fournie par @KennyTMs fonctionne très bien, mais si un lambda n'a pas de paramètres, l'utilisation de l'index arg <0> ne se compile pas. Si quelqu'un d'autre avait ce problème, j'ai une solution simple (plus simple que d'utiliser des solutions liées à SFINAE, c'est-à-dire).

Ajoutez simplement void à la fin du Tuple dans la structure arg après les types d'arguments variadic. c'est à dire.

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

puisque l'arité ne dépend pas du nombre réel de paramètres de modèle, le réel ne sera pas incorrect, et si c'est 0, alors au moins arg <0> existera toujours et vous pouvez en faire ce que vous voulez. Si vous prévoyez déjà de ne pas dépasser l'index arg<arity-1> alors cela ne devrait pas interférer avec votre implémentation actuelle.

1
Jon Koelzer