64 votes

Comment développer un tuple dans les arguments d'une fonction de modèle variadique ?

Considérons le cas d'une fonction modèle avec des arguments modèles variadiques :

template<typename Tret, typename... T> Tret func(const T&... t);

Maintenant, j'ai un tuple t de valeurs. Comment puis-je appeler func() en utilisant les valeurs des tuple comme arguments ? J'ai lu des articles sur le bind() l'objet de la fonction, avec call() ainsi que la fonction apply() dans différents documents aujourd'hui obsolètes. L'implémentation de la GNU GCC 4.4 semble avoir une call() dans le bind() mais il existe très peu de documentation sur le sujet.

Certaines personnes suggèrent des bidouillages récursifs écrits à la main, mais la véritable valeur des arguments variadiques des modèles est de pouvoir les utiliser dans des cas comme celui-ci.

Quelqu'un a-t-il une solution à ce problème, ou un indice sur l'endroit où lire à ce sujet ?

33voto

David Points 1773

Voici mon code si quelqu'un est intéressé

En gros, au moment de la compilation, le compilateur va dérouler récursivement tous les arguments dans les différents appels de fonctions inclusives <N> -> appels <N-1> -> appels ... -> appels <0> qui est le dernier et le compilateur va optimiser les différents appels de fonctions intermédiaires pour ne garder que le dernier qui est l'équivalent de func(arg1, arg2, arg3, ...).

Il existe 2 versions, l'une pour une fonction appelée sur un objet et l'autre pour une fonction statique.

#include <tr1/tuple>

/**
 * Object Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_obj_func
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_obj_func<N-1>::applyTuple( pObj, f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_obj_func<0>
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    (pObj->*f)( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename T, typename... ArgsF, typename... ArgsT >
void applyTuple( T* pObj,
                 void (T::*f)( ArgsF... ),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_obj_func<sizeof...(ArgsT)>::applyTuple( pObj, f, t );
}

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_func
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_func<N-1>::applyTuple( f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_func<0>
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    f( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename... ArgsF, typename... ArgsT >
void applyTuple( void (*f)(ArgsF...),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_func<sizeof...(ArgsT)>::applyTuple( f, t );
}

// ***************************************
// Usage
// ***************************************

template < typename T, typename... Args >
class Message : public IMessage
{

  typedef void (T::*F)( Args... args );

public:

  Message( const std::string& name,
           T& obj,
           F pFunc,
           Args... args );

private:

  virtual void doDispatch( );

  T*  pObj_;
  F   pFunc_;
  std::tr1::tuple<Args...> args_;
};

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
Message<T, Args...>::Message( const std::string& name,
                              T& obj,
                              F pFunc,
                              Args... args )
: IMessage( name ),
  pObj_( &obj ),
  pFunc_( pFunc ),
  args_( std::forward<Args>(args)... )
{

}

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
void Message<T, Args...>::doDispatch( )
{
  try
  {
    applyTuple( pObj_, pFunc_, args_ );
  }
  catch ( std::exception& e )
  {

  }
}

25voto

sigidagi Points 520

En C++, il existe de nombreuses façons d'étendre/développer un tuple et d'appliquer ces éléments de tuple à une fonction template variadique. Voici une petite classe d'aide qui crée un tableau d'index. Elle est très utilisée dans la métaprogrammation des modèles :

// ------------- UTILITY---------------
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

Maintenant, le code qui fait le travail n'est pas si grand :

 // ----------UNPACK TUPLE AND APPLY TO FUNCTION ---------
#include <tuple>
#include <iostream> 

using namespace std;

template<class Ret, class... Args, int... Indexes > 
Ret apply_helper( Ret (*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup) 
{ 
    return pf( forward<Args>( get<Indexes>(tup))... ); 
} 

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), const tuple<Args...>&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), tuple<Args...>(tup));
}

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), tuple<Args...>&&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), forward<tuple<Args...>>(tup));
}

Le test est présenté ci-dessous :

// --------------------- TEST ------------------
void one(int i, double d)
{
    std::cout << "function one(" << i << ", " << d << ");\n";
}
int two(int i)
{
    std::cout << "function two(" << i << ");\n";
    return i;
}

int main()
{
    std::tuple<int, double> tup(23, 4.5);
    apply(one, tup);

    int d = apply(two, std::make_tuple(2));    

    return 0;
}

Je ne suis pas un grand spécialiste des autres langues, mais je suppose que si ces langues n'ont pas cette fonctionnalité dans leur menu, il n'y a aucun moyen de le faire. Au moins avec C++ vous pouvez, et je pense que ce n'est pas si compliqué...

17voto

DRayX Points 451

Je trouve que c'est la solution la plus élégante (et elle est transmise de manière optimale) :

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a)
        -> decltype(Apply<N-1>::apply(
            ::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        ))
    {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a)
        -> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
    {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t)
    -> decltype(Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
{
    return Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

Malheureusement, GCC (4.6 au moins) ne parvient pas à le compiler avec "sorry, unimplemented : mangling overload" (ce qui signifie simplement que le compilateur n'implémente pas encore complètement la spécification C++11), et comme il utilise des templates variadiques, il ne fonctionnera pas dans MSVC, il est donc plus ou moins inutile. Cependant, dès qu'il y aura un compilateur qui supportera la spécification, ce sera la meilleure approche IMHO. (Note : il n'est pas très difficile de modifier ceci afin de contourner les déficiences de GCC, ou de l'implémenter avec le préprocesseur Boost, mais cela ruine l'élégance, c'est donc la version que je poste).

GCC 4.7 supporte maintenant très bien ce code.

Edit : Ajout d'un forward autour de l'appel de fonction actuel pour supporter la forme de référence rvalue *au cas où vous utiliseriez clang (ou si quelqu'un d'autre arrive à l'ajouter).

Edit : Ajout du forward manquant autour de l'objet fonction dans le corps de la fonction apply non membre. Merci à pheedbaq d'avoir signalé qu'il manquait.

Edit : Et voici la version C++14 juste parce qu'elle est tellement plus belle (elle ne compile pas encore) :

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a) {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a) {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t) {
    return Apply< ::std::tuple_size< ::std::decay_t<T>
      >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

8voto

PeterSom Points 857
template<typename F, typename Tuple, std::size_t ... I>
auto apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename Tuple>
auto apply(F&& f, Tuple&& t) {
    using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}

Ceci est adapté du projet C++14 en utilisant index_sequence. Je pourrais proposer de l'appliquer dans une future norme (TS).

2voto

Daniel Earwicker Points 63298

Les nouvelles ne sont pas bonnes.

Après avoir lu le projet de norme qui vient d'être publié Je ne vois pas de solution intégrée à ce problème, ce qui me semble étrange.

Le meilleur endroit pour poser ce genre de questions (si vous ne l'avez pas déjà fait) est comp.lang.c++.moderated, car certaines personnes impliquées dans la rédaction de la norme y postent régulièrement.

Si vous regardez este hilo quelqu'un s'est posé la même question (peut-être que c'est vous, auquel cas vous allez trouver cette réponse un peu frustrante !), et quelques implémentations grossières sont suggérées.

Je me suis juste demandé s'il ne serait pas plus simple de faire accepter à la fonction un tuple car la conversion est plus facile de cette façon. Mais cela implique que toutes les fonctions devraient accepter les tuples comme arguments, pour une flexibilité maximale, et donc cela démontre simplement l'étrangeté de ne pas fournir une expansion intégrée du paquet de tuple en argument de fonction.

Mise à jour : le lien ci-dessus ne fonctionne pas - essayez de coller ceci :

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/750fa3815cdaac45/d8dc09e34bbb9661?lnk=gst&q=tuple+variadic#d8dc09e34bbb9661

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X