Spécialisation d'un modèle sur un lambda en C++0x

j'ai écrit une classe de traits qui me permet d'extraire des informations sur les arguments et le type d'une fonction ou d'un objet de fonction en C++0x (testé avec gcc 4.5.0). Le cas général gère les objets de fonction:

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

alors j'ai une spécialisation pour les fonctions simples à la portée globale:

template <typename R, typename... A>
struct function_traits<R (*)(A...)> {
    // ...
};

cela fonctionne très bien, je peux passer une fonction dans le modèle ou un objet de fonction et il fonctionne correctement:

template <typename F>
void foo(F f) {
    typename function_traits<F>::whatever ...;
}

int f(int x) { ... }
foo(f);

que se passe-t-il si, au lieu de passer une fonction ou un objet de fonction dans foo , je veux passer une expression lambda?

foo([](int x) { ... });

le problème ici est qu'aucune des deux spécialisations de function_traits<> ne s'applique. Le projet C++0x indique que le type de l'expression est un "type de classe unique, sans nom, non syndiqué". Démangler le résultat de l'appel typeid(...).name() sur l'expression me donne ce qui semble être la Convention interne d'appellation de gcc pour la lambda, main::{lambda(int)#1} , pas quelque chose qui représente syntaxiquement un nom de type C++.

en bref, y a-t-il quelque chose que je puisse mettre dans le modèle ici:

template <typename R, typename... A>
struct function_traits<????> { ... }

qui permettra à cette classe de traits d'accepter une expression lambda?

22
demandé sur Georg Fritzsche 2010-04-01 21:32:09

3 réponses

je pense qu'il est possible de spécialiser les traits pour lambdas et faire la correspondance de modèle sur la signature du functor sans nom. Voici le code qui fonctionne sur g++ 4.5. Bien que cela fonctionne, la correspondance de modèle sur lambda semble fonctionner contrairement à l'intuition. J'ai des commentaires en ligne.

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; });
}
17
répondu Sumant 2010-04-02 04:46:40

l'astuce void_t peut aider. comment " void_t "fonctionne ?

sauf si vous avez C++17, vous devrez inclure la définition de void_t :

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

Ajouter un argument de modèle supplémentaire au modèle original, par défaut à void :

template <typename T, typename = void>
struct function_traits;

l'objet traits pour les fonctions simples est le même que vous avez déjà:

template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};

Pour les méthodes non 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... >;
};

N'oubliez pas const méthodes:

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

enfin, le trait important. Étant donné un type de classe, y compris les types lambda, nous voulons passer de T à decltype(&T::operator()) . Nous voulons nous assurer que ce trait n'est disponible que pour les types T pour lesquels ::operator() est disponible, et c'est ce que void_t fait pour nous. Pour appliquer cette contrainte, nous devons mettre &T::operator() dans la signature de trait quelque part, d'où 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())   >
{
};

la méthode de l'opérateur() dans (non - mutable , non-générique) lambdas est const , ce qui explique pourquoi nous avons besoin du modèle const ci-dessus.

mais en fin de compte, c'est très restrictif. Cela ne fonctionnera pas avec lambdas générique, ou des objets avec le modèle operator() . Si vous reconsidérer votre conception, vous trouverez de trouver une approche différente, plus souple.

2
répondu Aaron McDaid 2017-05-23 12:34:12

en déléguant une partie du travail à une série de modèles de fonction au lieu d'un modèle de classe , vous pouvez extraire les informations pertinentes.

mais d'Abord, je dois dire que la méthode est un const la méthode, pour un lambda (pour un non-capture, non générique, non mutable lambda). Vous ne pourrez donc pas faire la différence entre un vrai lambda et ceci:

struct {
    int operator() (int) const { return 7; }
} object_of_unnamed_name_and_with_suitable_method;

Par conséquent, je dois supposer que vous ne voulez pas "traitement spécial" pour lambdas, et vous ne voulez pas tester si un type est un type lambda, et que vous voulez simplement extraire le type de retour, et le type de tous les arguments, pour tout objet qui est assez simple. Par "assez simple", je veux dire, par exemple, que la méthode operator() est et non elle-même un modèle. Et, pour information bonus, un booléen pour nous dire si une méthode operator() était présentes et utilisées, par opposition à un simple vieux de la fonction.



// 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>() ) )
{
};
1
répondu Aaron McDaid 2016-09-22 18:51:13