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

135

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

Fondamentalement, je veux lambda_traitsce 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_traitsdans 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::functioncomme:

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 TLambdaversion du modèle de fonction et je souhaite construire l' std::functionobjet à l'intérieur de la fonction (comme indiqué ci-dessus).

Nawaz
la source
Si vous connaissez le type de paramètre, cela peut être utilisé pour déterminer le type de retour. Cependant, je ne sais pas comment déterminer le type de paramètre.
Mankarse
Est-il supposé que la fonction prend un seul argument?
iammilind
1
"type de paramètre" Mais une fonction lambda arbitraire n'a pas de type de paramètre. Cela peut prendre n'importe quel nombre de paramètres. Ainsi, toute classe de traits devrait être conçue pour interroger les paramètres par indices de position.
Nicol Bolas
@iammilind: Oui. pour le moment, nous pouvons le supposer.
Nawaz
@NicolBolas: Pour le moment, nous pouvons supposer que le lambda prend exactement un argument.
Nawaz

Réponses:

160

Drôle, je viens d'écrire une function_traitsimplémentation basée sur Spécialiser 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 des lambda . decltypeoperator()

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) {}.

KennyTM
la source
Heh, j'écrivais juste ceci. Je n'ai pas réfléchi tuple_element, merci.
GManNickG
@GMan: Si votre approche n'est pas exactement la même que celle-ci, veuillez la poster alors. Je vais tester cette solution.
Nawaz
3
Un trait complet utiliserait également une spécialisation pour non- const, pour ceux déclarés par lambda mutable( []() mutable -> T { ... }).
Luc Danton
1
@Andry c'est un problème fondamental avec les objets de fonction qui ont (potentiellement) plusieurs surcharges ou operator()pas avec cette implémentation. auton'est pas un type, donc ça ne peut jamais être la réponse àtraits::template arg<0>::type
Caleth
1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib / ... Comme solution aux liens brisés: essayez de rechercher à partir de la racine, Luke.
Andry
11

Bien que je ne sois pas sûr que ce soit strictement conforme à la norme, 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, de sorte que le résultat et les types de paramètres doivent en être extraits. Si vous pouvez utiliser boost::function_traits, result_typeet arg1_type répondra à l'objectif. Comme ideone ne semble pas fournir de boost en mode C ++ 11, je n'ai pas pu publier le code réel, désolé.

Ise Wisteria
la source
1
Je pense que c'est un bon début. +1 pour ça. Nous devons maintenant travailler sur le type de fonction pour extraire les informations requises. (Je ne veux pas utiliser Boost pour le moment, car je veux apprendre les choses).
Nawaz
6

La méthode de spécialisation présenté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 les variadiques operator(). Au lieu de cela, on peut également envisager is_variadic.

Columbo
la source
1

La réponse fournie par @KennyTMs fonctionne très bien, cependant si un lambda n'a pas de paramètres, l'utilisation de l'index arg <0> ne 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 variadiques. c'est à dire

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

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

Jon Koelzer
la source