Comment étendre un tuple en arguments de fonction de modèle variadique?


136

Prenons le cas d'une fonction basée sur un modèle avec des arguments de modèle variadiques:

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

Maintenant, j'ai un tuple tde valeurs. Comment appeler en func()utilisant les valeurs de tuple comme arguments? J'ai lu sur l' bind()objet fonction, avec call()fonction, et aussi sur la apply()fonction dans différents documents maintenant obsolètes. L'implémentation de GNU GCC 4.4 semble avoir une call()fonction dans la bind()classe, mais il y a très peu de documentation sur le sujet.

Certaines personnes suggèrent des hacks récursifs écrits à la main, mais la vraie valeur des arguments de modèle variadique est de pouvoir les utiliser dans des cas comme ci-dessus.

Quelqu'un a-t-il une solution ou un indice sur où en savoir plus?


5
Le standard C ++ 14 a une solution voir; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen

1
L'idée est de décompresser le tuple en une seule explosion variadique, en utilisant integer_sequence, voir en.cppreference.com/w/cpp/utility/integer_sequence
Skeen

6
Avec un integer_sequence S, vous appelez simplement votre fonction en tant que func(std::get<S>(tuple)...)et laissez le compilateur gérer le reste.
Skeen

1
Si vous utilisez C ++ 17 ou une version ultérieure, ignorez cette réponse et voyez celle ci-dessous en utilisant std :: apply
lewis

Réponses:


46

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

Fondamentalement, au moment de la compilation, le compilateur déroulera récursivement tous les arguments dans divers appels de fonction inclusifs <N> -> appelle <N-1> -> appelle ... -> appelle <0> qui est le dernier et le compilateur optimisera loin les différentes fonctions intermédiaires appellent à ne garder que la dernière qui est l'équivalent de func (arg1, arg2, arg3, ...)

Deux versions sont fournies, 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 )
  {

  }
}

2
Est-il possible de l'adapter pour fonctionner dans un cas où la "fonction" en question est en fait un constructeur?
HighCommander4

Pourriez-vous nous donner un exemple de ce que vous voulez faire et nous pourrons partir de là.
David

Cette solution ne présente qu'un surcoût de temps de compilation et à la fin elle sera simplifiée en (pObj -> * f) (arg0, arg, 1, ... argN); droite?
Goofy

oui, le compilateur compressera les multiples appels de fonction dans le dernier comme si vous l'aviez écrit vous-même, ce qui est la beauté de tout ce truc de méta-programmation.
David

tout tr1peut être retiré maintenant avec c ++ 11
Ryan Haining

37

En C ++ 17, vous pouvez faire ceci:

std::apply(the_function, the_tuple);

Cela fonctionne déjà dans Clang ++ 3.9, en utilisant std :: experimental :: apply.

En réponse au commentaire disant que cela ne fonctionnera pas s'il the_functionest basé sur un modèle, voici une solution de contournement:

#include <tuple>

template <typename T, typename U> void my_func(T &&t, U &&u) {}

int main(int argc, char *argv[argc]) {

  std::tuple<int, float> my_tuple;

  std::apply([](auto &&... args) { my_func(args...); }, my_tuple);

  return 0;
}

Cette solution de contournement est une solution simplifiée au problème général du passage des ensembles de surcharge et du modèle de fonction là où une fonction serait attendue. La solution générale (celle qui prend en charge le transfert parfait, la constexpr-ness et le noexcept-ness) est présentée ici: https://blog.tartanllama.xyz/passing-overload-sets/ .


Selon l'exemple de code de std :: apply, il ne semble pas fonctionner s'il the_functionest basé sur un modèle.
Zitrax

1
@Zitrax Vous pouvez spécifier les arguments de modèle de la fonction:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Erbureth dit Réintégrer Monica

C'est la solution la plus simple et la plus élégante. Et cela fonctionne à merveille. Merci beaucoup, M. Alaggan !!!!!! +100 votes
Elliott

36

En C ++, il existe de nombreuses façons de développer / décompresser un tuple et d'appliquer ces éléments de tuple à une fonction de modèle variadique. Voici une petite classe d'assistance qui crée un tableau d'index. Il est beaucoup utilisé dans la métaprogrammation de 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 gros:

 // ----------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 montré 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 expert dans d'autres langues, mais je suppose que si ces langues n'ont pas une telle 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é ...


"... et appliquer ces éléments de tuple à une fonction de modèle variadique" . La section de test ne contient cependant que des fonctions variadiques non modèles. Si j'ajoute un comme template<class ... T> void three(T...) {}et que j'essaie d'utiliser, appliquez dessus, il ne compile pas.
Zitrax le

32

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

Exemple d'utilisation:

void foo(int i, bool b);

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&foo, t);
}

Malheureusement, GCC (4.6 au moins) ne parvient pas à compiler cela avec "désolé, non implémenté: 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 modèles variadiques, il ne sera pas travailler dans MSVC, donc c'est plus ou moins inutile. Cependant, une fois qu'il y aura un compilateur qui prend en charge la spécification, ce sera la meilleure approche à mon humble avis. (Remarque: ce n'est pas si difficile de modifier cela pour pouvoir contourner les lacunes de GCC, ou de l'implémenter avec Boost Preprocessor, mais cela ruine l'élégance, c'est donc la version que je publie.)

GCC 4.7 prend désormais en charge ce code très bien.

Edit: Ajout de l'avant autour de l'appel de fonction réel pour prendre en charge le formulaire de référence rvalue * au cas où vous utiliseriez clang (ou si quelqu'un d'autre se contente de l'ajouter).

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

Edit: Et voici la version C ++ 14 juste parce qu'elle est tellement plus agréable (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));
}

Voici une version pour les fonctions membres (peu testée!):

using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.

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

template<>
struct ApplyMember<0>
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
        decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
    {
        return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
    }
};

// C is the class, F is the member function, T is the tuple.
template<typename C, typename F, typename T>
inline auto apply(C&& c, F&& f, T&& t) ->
    decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
{
    return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
}
// Example:

class MyClass
{
public:
    void foo(int i, bool b);
};

MyClass mc;

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&mc, &MyClass::foo, t);
}

1
+1 sur les réponses répertoriées, la vôtre était la plus proche que je puisse obtenir pour travailler avec des arguments dont les arguments sont des vecteurs ... ... mais j'obtiens toujours des erreurs de compilation. ideone.com/xH5kBH Si vous compilez ceci avec -DDIRECT_CALL et que vous l'exécutez, vous verrez ce que la sortie devrait être. Sinon, j'obtiens une erreur de compilation (je pense que decltype n'est pas assez intelligent pour comprendre mon cas particulier), avec gcc 4.7.2.
kfmfe04

3
La version de gcc sur ideaone est trop ancienne pour que cela passe, elle ne prend pas en charge la surcharge de type de retour decltype mutilée. J'ai testé ce code de manière relativement approfondie dans gcc 4.7.2, et je n'ai rencontré aucun problème. Avec gcc 4.8, vous pouvez utiliser la nouvelle fonctionnalité de valeur de retour automatique C ++ 17 pour éviter tous les types de retour de fin decltype désagréables.
DRayX

1
Par curiosité, dans la fonction non membre apply, pourquoi n'est fpas encapsulé avec un std::forwardappel, comme c'est le cas dans le type de retour? N'est-ce pas nécessaire?
Brett Rossier

3
Par curiosité, j'ai essayé de compiler ceci dans GCC 4.8, et j'ai foo('x', true)compilé exactement le même code d'assemblage apply(foo, ::std::make_tuple('x', true))qu'avec n'importe quel niveau d'optimisation en plus de -O0.
DRayX

2
Avec C ++ 14, integer_sequencevous obtenez même une implémentation presque correcte de apply()dans son exemple. voir ma réponse ci-dessous.
PeterSom le

28
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 brouillon de C ++ 14 en utilisant index_sequence. Je pourrais proposer de l'appliquer dans une future norme (TS).


1

Les nouvelles ne semblent pas bonnes.

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

Le meilleur endroit pour poser des questions sur de telles choses (si vous ne l'avez pas déjà fait) est comp.lang.c ++. Modéré, car certaines personnes participent régulièrement à la rédaction du message standard.

Si vous consultez ce fil , quelqu'un a la même question (peut-être que c'est vous, auquel cas vous allez trouver toute cette réponse un peu frustrante!), Et quelques implémentations horribles sont suggérées.

Je me suis juste demandé s'il serait plus simple de faire accepter la fonction a tuple, car la conversion de cette façon est plus facile. Mais cela implique que toutes les fonctions doivent accepter les tuples comme arguments, pour une flexibilité maximale, et cela démontre simplement l'étrangeté de ne pas fournir une extension intégrée du tuple en pack d'arguments 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


Je me demande pourquoi ils prennent même la peine d'avoir des notions distinctes de pack d'arguments tuple et fonction. Peut-être que dans un compilateur conforme, ils sont interchangeables mais je n'ai pas repéré d'indication à ce sujet partout où j'ai lu à leur sujet.
Daniel Earwicker

2
Parce que tuple <int, char, string> est nécessaire en tant que type séparé; tout comme la possibilité de créer une fonction qui ne nécessite pas de make_type au milieu de chaque appel.
coppro

1
De plus, le meilleur endroit n'est pas comp.lang.c ++. Modéré. Les questions sur C ++ 1x sont presque toujours mieux adressées à comp.std.c ++.
coppro

1

Toutes ces implémentations sont bonnes. Mais en raison de l'utilisation d'un pointeur vers une fonction membre, le compilateur ne peut souvent pas insérer l'appel de la fonction cible (au moins gcc 4.8 ne peut pas, quoi qu'il en soit Pourquoi gcc ne peut pas insérer des pointeurs de fonction qui peuvent être déterminés? )

Mais les choses changent si vous envoyez le pointeur vers la fonction membre en tant qu'arguments de modèle, pas en tant que paramètres de fonction:

/// from https://stackoverflow.com/a/9288547/1559666
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

template<typename TT>
using makeSeq = typename gens< std::tuple_size< typename std::decay<TT>::type >::value >::type;


// deduce function return type
template<class ...Args>
struct fn_type;

template<class ...Args>
struct fn_type< std::tuple<Args...> >{

    // will not be called
    template<class Self, class Fn>
    static auto type_helper(Self &self, Fn f) -> decltype((self.*f)(declval<Args>()...)){
        //return (self.*f)(Args()...);
        return NULL;
    }
};

template<class Self, class ...Args>
struct APPLY_TUPLE{};

template<class Self, class ...Args>
struct APPLY_TUPLE<Self, std::tuple<Args...>>{
    Self &self;
    APPLY_TUPLE(Self &self): self(self){}

    template<class T, T (Self::* f)(Args...),  class Tuple>
    void delayed_call(Tuple &&list){
        caller<T, f, Tuple >(forward<Tuple>(list), makeSeq<Tuple>() );
    }

    template<class T, T (Self::* f)(Args...), class Tuple, int ...S>
    void caller(Tuple &&list, const seq<S...>){
        (self.*f)( std::get<S>(forward<Tuple>(list))... );
    }
};

#define type_of(val) typename decay<decltype(val)>::type

#define apply_tuple(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            > \
            (tuple);

Et utilisation:

struct DelayedCall
{  
    void call_me(int a, int b, int c){
        std::cout << a+b+c;
    }

    void fire(){
        tuple<int,int,int> list = make_tuple(1,2,3);
        apply_tuple(*this, call_me, list); // even simpler than previous implementations
    }
};

Preuve d'inlinable http://goo.gl/5UqVnC


Avec de petits changements, on peut "surcharger" apply_tuple:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(X,##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define VARARG_IMPL_(base, count, ...) base##count(__VA_ARGS__)
#define VARARG_IMPL(base, count, ...) VARARG_IMPL_(base, count, __VA_ARGS__)
#define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)

#define apply_tuple2(fname, tuple) apply_tuple3(*this, fname, tuple)
#define apply_tuple3(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            /* ,decltype(tuple) */> \
            (tuple);
#define apply_tuple(...) VARARG(apply_tuple, __VA_ARGS__)

...

apply_tuple(obj, call_me, list);
apply_tuple(call_me, list);       // call this->call_me(list....)

De plus, c'est la seule solution qui fonctionne avec des fonctions basées sur des modèles.


1

1) si vous avez une structure parameter_pack prête à l'emploi comme argument de fonction, vous pouvez simplement utiliser std :: tie comme ceci:

template <class... Args>
void tie_func(std::tuple<Args...> t, Args&... args)
{
 std::tie<Args...>(args...) = t;
}

int main()
{
 std::tuple<int, double, std::string> t(2, 3.3, "abc");

 int i;
 double d;
 std::string s;

 tie_func(t, i, d, s);

 std::cout << i << " " << d << " " << s << std::endl;
}

2) Si vous n'avez pas d'argument parampack prêt à l'emploi, vous devrez dérouler le tuple comme ceci

#include <tuple>
#include <functional>
#include <iostream>



template<int N>
struct apply_wrap {
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>& t, UnpackedArgs... args )
    {
        return apply_wrap<N-1>::applyTuple( f, t, std::get<N-1>( t ), args... );
    }
};


template<>
struct apply_wrap<0>
{
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>&, UnpackedArgs... args )
    {
        return f( args... );
    }
};



template<typename R, typename... TupleArgs>
R applyTuple( std::function<R(TupleArgs...)>& f, std::tuple<TupleArgs...> const& t )
{
    return apply_wrap<sizeof...(TupleArgs)>::applyTuple( f, t );
}



int fac(int n)
{
    int r=1;
    for(int i=2; i<=n; ++i)
        r *= i;
    return r;
}



int main()
{
    auto t = std::make_tuple(5);
    auto f = std::function<decltype(fac)>(&fac);
    cout << applyTuple(f, t);
}

0

Que dis-tu de ça:

// Warning: NOT tested!
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

using std::declval;
using std::forward;
using std::get;
using std::integral_constant;
using std::size_t;
using std::tuple;

namespace detail
{
    template < typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, 0u>, tuple<T...> const &t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    { return forward<Func>( f )( forward<Args>(a)... ); }

    template < size_t Index, typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, Index>, tuple<T...> const&t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    {
        return explode_tuple( integral_constant<size_t, Index - 1u>{}, t,
         forward<Func>(f), get<Index - 1u>(t), forward<Args>(a)... );
    }
}

template < typename Func, typename ...T >
auto  run_tuple( Func &&f, tuple<T...> const &t )
 -> decltype( forward<Func>(f)(declval<T const>()...) )
{
    return detail::explode_tuple( integral_constant<size_t, sizeof...(T)>{}, t,
     forward<Func>(f) );
}

template < typename Tret, typename ...T >
Tret  func_T( tuple<T...> const &t )
{ return run_tuple( &func<Tret, T...>, t ); }

Le run_tuplemodèle de fonction prend le tuple donné et transmet ses éléments individuellement à la fonction donnée. Il effectue son travail en appelant de manière récursive ses modèles de fonctions d'assistance explode_tuple. Il est important de run_tupletransmettre la taille du tuple à explode_tuple; ce nombre agit comme un compteur pour le nombre d'éléments à extraire.

Si le tuple est vide, run_tupleappelle la première version de explode_tupleavec la fonction distante comme seul autre argument. La fonction distante est appelée sans argument et nous avons terminé. Si le tuple n'est pas vide, un nombre plus élevé est passé à la deuxième version de explode_tuple, avec la fonction distante. Un appel récursif àexplode_tupleest fait, avec les mêmes arguments, sauf que le nombre de compteur est diminué de un et (une référence à) le dernier élément de tuple est ajouté comme argument après la fonction distante. Dans un appel récursif, soit le compteur n'est pas nul, et un autre appel est effectué avec le compteur diminué à nouveau et l'élément suivant non référencé est inséré dans la liste d'arguments après la fonction distante mais avant les autres arguments insérés, soit le compteur atteint zéro et la fonction distante est appelée avec tous les arguments accumulés après elle.

Je ne suis pas sûr d'avoir la syntaxe pour forcer une version particulière d'un modèle de fonction à droite. Je pense que vous pouvez utiliser un pointeur vers une fonction comme objet de fonction; le compilateur le corrigera automatiquement.


0

J'évalue MSVS 2013RC, et il n'a pas réussi à compiler certaines des solutions précédentes proposées ici dans certains cas. Par exemple, MSVS ne parviendra pas à compiler les retours «auto» s'il y a trop de paramètres de fonction, en raison d'une limite d'imbrication d'espace de noms (j'ai envoyé cette information à Microsoft pour qu'elle soit corrigée). Dans d'autres cas, nous avons besoin d'accéder au retour de la fonction, bien que cela puisse également être fait avec un lamda: les deux exemples suivants donnent le même résultat.

apply_tuple([&ret1](double a){ret1 = cos(a); }, std::make_tuple<double>(.2));
ret2 = apply_tuple((double(*)(double))cos, std::make_tuple<double>(.2));

Et merci encore à ceux qui ont posté des réponses ici avant moi, je ne serais pas arrivé à ça sans cela ... alors la voici:

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

// This is a work-around for MSVS 2013RC that is required in some cases
#if _MSC_VER <= 1800 /* update this when bug is corrected */
template<>
struct apply_impl<6> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&& t, A&&... a)
    -> decltype(std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
};
#endif

template<>
struct apply_impl<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(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 C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&&, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::forward<A>(a)...);
    }
};

// Apply tuple parameters on a non-member or static-member function by perfect forwarding
template<typename F, typename T>
inline auto apply_tuple(F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t));
}

// Apply tuple parameters on a member function
template<typename C, typename F, typename T>
inline auto apply_tuple(C*const o, F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t));
}

Pourquoi faire de l'argument objet un pointeur const? Pas de référence, pas de référence const, pas seulement un pointeur? Et si la fonction appelable ne fonctionnait pas const?
tower120

0

En vous étendant sur la solution de @ David, vous pouvez écrire un modèle récursif qui

  1. N'utilise pas la integer_sequencesémantique (trop verbeuse, imo)
  2. N'utilise pas de paramètre de modèle temporaire supplémentaire int Npour compter les itérations récursives
  3. (Facultatif pour les foncteurs statiques / globaux) utilise le foncteur comme paramètre de modèle pour l'optimisation à la compilation

Par exemple:

template <class F, F func>
struct static_functor {
    template <class... T, class... Args_tmp>
    static inline auto apply(const std::tuple<T...>& t, Args_tmp... args)
            -> decltype(func(std::declval<T>()...)) {
        return static_functor<F,func>::apply(t, args...,
                std::get<sizeof...(Args_tmp)>(t));
    }
    template <class... T>
    static inline auto apply(const std::tuple<T...>& t, T... args)
            -> decltype(func(args...)) {
        return func(args...);
    }
};

static_functor<decltype(&myFunc), &myFunc>::apply(my_tuple);

Alternativement, si votre foncteur n'est pas défini au moment de la compilation (par exemple, une constexprinstance non fonctionnelle ou une expression lambda), vous pouvez l'utiliser comme paramètre de fonction au lieu d'un paramètre de modèle de classe, et en fait supprimer entièrement la classe contenant:

template <class F, class... T, class... Args_tmp>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        Args_tmp... args) -> decltype(func(std::declval<T>()...)) {
    return apply_functor(func, t, args..., std::get<sizeof...(Args_tmp)>(t));
}
template <class F, class... T>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        T... args) -> decltype(func(args...)) {
    return func(args...);
}

apply_functor(&myFunc, my_tuple);

Pour les callables de fonction pointeur vers membre, vous pouvez ajuster l'un des morceaux de code ci-dessus de la même manière que dans la réponse de @ David.

Explication

En référence au deuxième morceau de code, il existe deux fonctions de modèle: la première prend le foncteur func, le tuple tavec les types T...et un pack argsde paramètres de types Args_tmp.... Lorsqu'il est appelé, il ajoute de manière récursive les objets du tpack de paramètres un à la fois, du début ( 0) à la fin, et appelle à nouveau la fonction avec le nouveau pack de paramètres incrémenté.

La signature de la deuxième fonction est presque identique à la première, sauf qu'elle utilise le type T...pour le pack de paramètres args. Ainsi, une fois que argsla première fonction est complètement remplie avec les valeurs de t, son type sera T...(dans psuedo-code, typeid(T...) == typeid(Args_tmp...)), et donc le compilateur appellera à la place la deuxième fonction surchargée, qui à son tour appelle func(args...).

Le code de l'exemple de foncteur statique fonctionne de manière identique, le foncteur étant à la place utilisé comme argument de modèle de classe.


tout commentaire sur l'optimisation au moment de la compilation de la première option serait apprécié, afin que je puisse rendre ma réponse plus complète (et peut-être apprendre quelque chose de nouveau).
CrepeGoat du

-3

Pourquoi ne pas simplement envelopper vos arguments variadiques dans une classe de tuple, puis utiliser la récursivité au moment de la compilation (voir le lien ) pour récupérer l'index qui vous intéresse. Je trouve que décompresser les modèles variadiques dans un conteneur ou une collection peut ne pas être de type sûr pour des types hétérogènes

template<typename... Args>
auto get_args_as_tuple(Args... args) -> std::tuple<Args...> 
{
    return std::make_tuple(args);
}

6
La question était l'inverse. Pas Args...-> tuple, mais tuple-> Args....
Xeo

-4

Cette solution simple fonctionne pour moi:

template<typename... T>
void unwrap_tuple(std::tuple<T...>* tp)
{
    std::cout << "And here I have the tuple types, all " << sizeof...(T) << " of them" << std::endl;
}

int main()
{
    using TupleType = std::tuple<int, float, std::string, void*>;

    unwrap_tuple((TupleType*)nullptr); // trick compiler into using template param deduction
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.