Come faccio ad espandere una tupla negli argomenti della funzione del modello variadico?


136

Considera il caso di una funzione basata su modelli con argomenti modello variadici:

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

Ora ho una tupla tdi valori. Come posso chiamare func()usando i valori di tupla come argomenti? Ho letto bind()sull'oggetto funzione, con call()funzione, e anche la apply()funzione in diversi documenti ormai obsoleti. L'implementazione di GNU GCC 4.4 sembra avere una call()funzione nella bind()classe, ma c'è pochissima documentazione sull'argomento.

Alcune persone suggeriscono degli hack ricorsivi scritti a mano, ma il vero valore degli argomenti dei modelli variadici è di poterli usare in casi come sopra.

Qualcuno ha una soluzione o è un suggerimento su dove leggere al riguardo?


5
Lo standard C ++ 14 ha una soluzione vedi; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen,

1
L'idea è di decomprimere la tupla in una singola esplosione variadica, usando integer_sequence, vedi en.cppreference.com/w/cpp/utility/integer_sequence
Skeen

6
Avere un integer_sequence S, basta chiamare la tua funzione come func(std::get<S>(tuple)...)e lasciare che il compilatore gestisca il resto.
Matassa

1
Se usi C ++ 17 o versioni successive, ignora questa risposta e vedi quella sotto usando std :: apply
lewis

Risposte:


46

Ecco il mio codice se qualcuno è interessato

Fondamentalmente in fase di compilazione il compilatore srotolerà ricorsivamente tutti gli argomenti in varie chiamate di funzione inclusive <N> -> chiamate <N-1> -> chiamate ... -> chiamate <0> che è l'ultima e il compilatore ottimizzerà via le varie funzioni intermedie chiamano solo per mantenere l'ultima che è l'equivalente di func (arg1, arg2, arg3, ...)

Sono disponibili 2 versioni, una per una funzione chiamata su un oggetto e l'altra per una funzione statica.

#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
È possibile adattarlo per funzionare in un caso in cui la "funzione" in questione è in realtà un costruttore?
HighCommander4,

Potresti fornire un esempio di ciò che vuoi fare e possiamo andare da lì.
David

Questa soluzione ha comportato solo un sovraccarico di tempo di compilazione e alla fine sarà semplificata in (pObj -> * f) (arg0, arg, 1, ... argN); giusto?
Pippo,

sì, il compilatore comprime le chiamate a più funzioni in quella finale come se l'avessi scritto tu stesso, il che è il bello di tutto questo materiale di meta-programmazione.
David,

tutte le tr1cose possono essere eliminate ora con c ++ 11
Ryan Haining,

37

In C ++ 17 puoi farlo:

std::apply(the_function, the_tuple);

Questo funziona già in Clang ++ 3.9, usando std :: sperimentale :: applicare.

Rispondendo al commento dicendo che questo non funzionerà se the_functionè stato creato un modello, la seguente è una soluzione:

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

Questa soluzione è una soluzione semplificata al problema generale del passaggio di set di sovraccarico e modello di funzione in cui ci si aspetterebbe una funzione. La soluzione generale (quella che si occupa di inoltro perfetto, constexpr-ness e noexcept-ness) è presentata qui: https://blog.tartanllama.xyz/passing-overload-sets/ .


Secondo il codice di esempio su std :: apply , non sembra funzionare se the_functionè stato creato un modello.
Zitrax,

1
@Zitrax Puoi specificare gli argomenti del template della funzione:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Erbureth dice Reinstate Monica

Questa è la soluzione più semplice ed elegante. E funziona a meraviglia. Grazie mille, M. Alaggan !!!!!! +100 voti
Elliott il

36

In C ++ ci sono molti modi per espandere / decomprimere la tupla e applicare quegli elementi tupla a una funzione modello variabile. Ecco una piccola classe di supporto che crea array di indici. È molto usato nella metaprogrammazione dei template:

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

Ora il codice che fa il lavoro non è così grande:

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

Il test è mostrato sotto:

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

Non sono un grande esperto di altre lingue, ma immagino che se queste lingue non hanno tale funzionalità nel loro menu, non c'è modo di farlo. Almeno con C ++ puoi, e penso che non sia così complicato ...


"... e applica quegli elementi tupla a una funzione di modello variadico" . La sezione test contiene solo funzioni variabili non template. Se ne aggiungo uno simile template<class ... T> void three(T...) {}e provo a utilizzare applica su che non viene compilato.
Zitrax,

32

Trovo che questa sia la soluzione più elegante (ed è inoltrata in modo ottimale):

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

Esempio di utilizzo:

void foo(int i, bool b);

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

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

Sfortunatamente GCC (almeno 4.6) non riesce a compilare questo con "scusa, non implementato: sovraccarico mangling" (il che significa semplicemente che il compilatore non implementa ancora completamente le specifiche C ++ 11), e dal momento che utilizza modelli variadic, non lo farà funziona in MSVC, quindi è più o meno inutile. Tuttavia, una volta che c'è un compilatore che supporta le specifiche, sarà l'approccio IMHO migliore. (Nota: non è così difficile modificarlo in modo da poter aggirare le carenze in GCC o implementarlo con Boost Preprocessor, ma rovina l'eleganza, quindi questa è la versione che sto postando.)

GCC 4.7 ora supporta bene questo codice.

Modifica: aggiunto inoltro attorno alla chiamata di funzione effettiva per supportare il modulo di riferimento del valore * questo nel caso in cui si stia utilizzando clang (o se qualcun altro effettivamente si aggira per aggiungerlo).

Modifica: aggiunto in avanti mancante attorno all'oggetto funzione nel corpo della funzione di applicazione non membro. Grazie a pheedbaq per aver sottolineato che mancava.

Modifica: Ed ecco la versione C ++ 14 proprio dal momento che è molto più bella (non è ancora stata compilata):

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

Ecco una versione per le funzioni membro (non testata molto!):

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 delle risposte elencate, la tua è stata la più vicina a cui sono riuscito a lavorare con argomenti i cui argomenti sono vettori ... ... ma sto ancora ricevendo errori di compilazione. ideone.com/xH5kBH Se lo compili con -DDIRECT_CALL e lo esegui, vedrai quale dovrebbe essere l'output. Ricevo un errore di compilazione altrimenti (penso che decltype non sia abbastanza intelligente da capire il mio caso speciale), con gcc 4.7.2.
kfmfe04,

3
La versione di gcc su ideaone è troppo vecchia perché questo passi, non supporta il sovraccarico del tipo di ritorno decltype malandato. Ho testato questo codice relativamente accuratamente in gcc 4.7.2 e non ho riscontrato alcun problema. Con gcc 4.8, puoi usare la nuova funzione di valore di ritorno automatico C ++ 17 per evitare tutti i cattivi tipi di ritorno finali decltype.
DRayX,

1
Per curiosità, nella applyfunzione non membro , perché fnon è racchiuso in una std::forwardchiamata, come nel tipo di ritorno? Non è necessario?
Brett Rossier,

3
Per curiosità, ho provato a compilarlo in GCC 4.8 e ho foo('x', true)compilato con lo stesso identico codice assembly apply(foo, ::std::make_tuple('x', true))con qualsiasi livello di ottimizzazione oltre a -O0.
DRayX,

2
Con C ++ 14 integer_sequencesi ottiene persino un'implementazione quasi corretta apply()nel suo esempio. vedi la mia risposta qui sotto.
PeterSom,

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

Questo è adattato dalla bozza C ++ 14 usando index_sequence. Potrei proporre di applicare in un futuro standard (TS).


1

Le notizie non sembrano buone.

Dopo aver letto la bozza dello standard appena pubblicato , non vedo una soluzione integrata a questo, che sembra strano.

Il posto migliore per chiedere cose del genere (se non l'hai già fatto) è moderato da comp.lang.c ++., Perché alcune persone coinvolte nella stesura del post standard lì regolarmente.

Se dai un'occhiata a questo thread , qualcuno ha la stessa domanda (forse sei tu, nel qual caso troverai tutta questa risposta un po 'frustrante!), E vengono suggerite alcune brutte implementazioni.

Mi chiedevo solo se sarebbe stato più semplice far accettare la funzione a tuple, poiché la conversione in quel modo è più semplice. Ma ciò implica che tutte le funzioni dovrebbero accettare le tuple come argomenti, per la massima flessibilità, e quindi ciò dimostra solo la stranezza di non fornire un'espansione integrata della tupla per il pacchetto di argomenti della funzione.

Aggiornamento: il link sopra non funziona - prova a incollare questo:

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


Mi chiedo perché si preoccupino persino di avere nozioni separate di tupla e pacchetto di argomenti funzionali. Forse in un compilatore conforme sono intercambiabili, ma non ho notato un'indicazione di ciò che ho letto su di loro.
Daniel Earwicker,

2
Perché tuple <int, char, string> è necessario come tipo separato; così come la possibilità di creare una funzione che non richiede make_type nel mezzo di ogni chiamata.
Coppro,

1
Inoltre, il posto migliore non è comp.lang.c ++. Moderato. Le domande su C ++ 1x sono quasi sempre meglio indirizzate a comp.std.c ++.
Coppro,

1

Tutte queste implementazioni sono buone. Ma a causa dell'uso del puntatore al compilatore di funzioni membro spesso non è possibile incorporare la chiamata della funzione di destinazione (almeno gcc 4.8 non può, indipendentemente dal motivo per cui gcc non può incorporare i puntatori di funzione che possono essere determinati? )

Ma le cose cambiano se si invia il puntatore alla funzione membro come argomenti del modello, non come parametri della funzione:

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

E ussage:

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

Prova di http://goo.gl/5UqVnC inlinabile


Con piccole modifiche, possiamo "sovraccaricare" 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....)

Inoltre questa è l'unica soluzione che funziona con funzioni basate su modelli.


1

1) se hai una struttura parametro_pacco readymade come argomento della funzione, puoi semplicemente usare std :: tie in questo modo:

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) se non disponi di un argomento parampack pronto, dovrai rilassare la tupla in questo modo

#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

Cosa ne pensi di questo:

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

Il run_tuplemodello di funzione prende la tupla data e passa i suoi elementi singolarmente alla funzione data. Svolge il suo lavoro chiamando in modo ricorsivo i suoi modelli di funzione di supporto explode_tuple. È importante che run_tuplepassi la dimensione della tupla aexplode_tuple ; quel numero funge da contatore per quanti elementi estrarre.

Se la tupla è vuota, run_tuplechiama la prima versione di explode_tuplecon la funzione remota come unico altro argomento. La funzione remota viene chiamata senza argomenti e il gioco è fatto. Se la tupla non è vuota, un numero superiore viene passato alla seconda versione di explode_tuple, insieme alla funzione remota. Una chiamata ricorsiva aexplode_tupleviene creato, con gli stessi argomenti, tranne per il fatto che il numero del contatore viene diminuito di uno e (un riferimento a) l'ultimo elemento tupla viene applicato come argomento dopo la funzione remota. In una chiamata ricorsiva, il contatore non è zero e viene effettuata un'altra chiamata con il contatore nuovamente diminuito e l'elemento successivo senza riferimento viene inserito nell'elenco degli argomenti dopo la funzione remota ma prima che gli altri argomenti inseriti, o il contatore raggiunga zero e la funzione remota viene chiamata con tutti gli argomenti accumulati dopo di essa.

Non sono sicuro di avere la sintassi di forzare una versione particolare di un modello di funzione giusto. Penso che puoi usare un puntatore a funzione come oggetto funzione; il compilatore lo riparerà automaticamente.


0

Sto valutando MSVS 2013RC e in alcuni casi non è stato possibile compilare alcune delle soluzioni precedenti proposte qui. Ad esempio, MSVS non riuscirà a compilare i ritorni "auto" se ci sono troppi parametri di funzione, a causa di un limite di imbrication dello spazio dei nomi (ho inviato le informazioni a Microsoft per correggerle). In altri casi, abbiamo bisogno di accedere al ritorno della funzione, sebbene ciò possa essere fatto anche con un lamda: i seguenti due esempi danno lo stesso risultato.

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

E grazie ancora a coloro che hanno pubblicato le risposte qui prima di me, non ci sarei arrivato senza di esso ... quindi eccolo qui:

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

Perché rendi l'argomento oggetto un puntatore const? Non riferimento, non riferimento costante, non solo puntatore? Che cosa succede se la funzione richiamabile non lo farà const?
tower120,

0

Estendendo la soluzione di @ David, puoi scrivere un modello ricorsivo che

  1. Non usa la integer_sequencesemantica (troppo prolissa, imo)
  2. Non utilizza un parametro modello temporaneo aggiuntivo int Nper contare le iterazioni ricorsive
  3. (Opzionale per i funzioni statici / globali) utilizza il funzione come parametro modello per l'ottimizzazione del tempo di compilazione

Per esempio:

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

In alternativa, se il tuo functor non è definito in fase di compilazione (ad constexpresempio, un'istanza non di funzione o un'espressione lambda), puoi usarlo come parametro di funzione anziché come parametro del modello di classe, e in effetti rimuovere completamente la classe contenente:

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

Per i callable puntatore-membro-funzione, è possibile regolare uno dei pezzi di codice sopra in modo simile come nella risposta di @ David.

Spiegazione

In riferimento al secondo pezzo di codice, ci sono due funzioni template: la prima prende il functor func, la tupla tcon i tipi T...e un pacchetto argsdi parametri di tipi Args_tmp.... Quando viene chiamato, aggiunge ricorsivamente gli oggetti dal tpacchetto di parametri uno alla volta, dall'inizio ( 0) alla fine, e chiama nuovamente la funzione con il nuovo pacchetto di parametri incrementato.

La firma della seconda funzione è quasi identica alla prima, tranne per il fatto che utilizza type T...per il pacchetto di parametri args. Quindi, una volta che argsla prima funzione è completamente riempita con i valori di t, il suo tipo sarà T...(in psuedo-code,typeid(T...) == typeid(Args_tmp...) ), e quindi il compilatore chiamerà invece la seconda funzione di overload, che a sua volta chiama func(args...).

Il codice nell'esempio di funzione statica funziona in modo identico, con la funzione invece utilizzata come argomento del modello di classe.


eventuali commenti sull'ottimizzazione in fase di compilazione della prima opzione sarebbero apprezzati, quindi posso rendere la mia risposta più completa (e forse imparare qualcosa di nuovo).
CrepeGoat,

-3

Perché non semplicemente avvolgere i tuoi argomenti variadici in una classe tupla e quindi utilizzare la ricorsione del tempo di compilazione (vedi link ) per recuperare l'indice che ti interessa. Trovo che decomprimere i modelli variadic in un contenitore o in una raccolta potrebbe non essere sicuro e scrivere tipi eterogenei

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

6
La domanda era al contrario. Non Args...-> tuple, ma tuple-> Args....
Xeo,

-4

Questa semplice soluzione funziona per me:

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.