Come puoi iterare sugli elementi di una std :: tuple?


112

Come posso iterare su una tupla (usando C ++ 11)? Ho provato quanto segue:

for(int i=0; i<std::tuple_size<T...>::value; ++i) 
  std::get<i>(my_tuple).do_sth();

ma questo non funziona:

Errore 1: mi dispiace, non implementato: impossibile espandere "Listener ..." in un elenco di argomenti di lunghezza fissa.
Errore 2: non posso apparire in un'espressione costante.

Quindi, come iterare correttamente sugli elementi di una tupla?


2
Posso chiederti come compili in C ++ 0x? Non è rilasciato né pronto per quanto ne so.
Burkhard

5
g ++ contiene il supporto sperimentale di alcune funzionalità di C ++ 0X, inclusi i modelli variadic, dalla versione 4.3. Altri compilatori fanno lo stesso (con diversi set di funzionalità, se vuoi usarli in produzione, sei tornato negli anni '90 con un'ampia variazione di supporto per cose all'avanguardia)
AProgrammer

Sto usando g ++ versione 4.4 con std = c ++ 0x

9
Questa domanda richiede un aggiornamento C ++ 11.
Omnifario

2
@Omnifarious ora, ha bisogno di un aggiornamento C ++ 14
pepper_chico

Risposte:


26

Boost.Fusion è una possibilità:

Esempio non testato:

struct DoSomething
{
    template<typename T>
    void operator()(T& t) const
    {
        t.do_sth();
    }
};

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());

@ViktorSehr AFAICT non lo fa (almeno su GCC 4.7.2)? Qualcuno con un suggerimento?
vedi l'

@ViktorSehr Trovato il problema: un bug / omissione fa sì che il comportamento della fusione dipenda dall'ordine degli include, vedere il ticket Boost # 8418 per maggiori dettagli
vedere l'

è necessario utilizzare boost :: fusion :: tuple invece di std :: tuple per farlo funzionare.
Marcin

Sotto GCC 8.1 / mingw-64, ottengo due avvisi per l'uso di boost :: fusion :: for_each con espressioni lambda std: boost / mpl / assert.hpp: 188: 21: warning: parentesi non necessarie nella dichiarazione di 'assert_arg' [-Wparentheses] non riuscito ************ (Pred :: ************ boost / mpl / assert.hpp: 193: 21: avvertenza: parentesi non necessarie tra dichiarazione di "assert_not_arg" [-Wparentheses] non riuscita ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein

129

Ho una risposta basata sull'iterazione su una tupla :

#include <tuple>
#include <utility> 
#include <iostream>

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  print(std::tuple<Tp...>& t)
  { }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  print(std::tuple<Tp...>& t)
  {
    std::cout << std::get<I>(t) << std::endl;
    print<I + 1, Tp...>(t);
  }

int
main()
{
  typedef std::tuple<int, float, double> T;
  T t = std::make_tuple(2, 3.14159F, 2345.678);

  print(t);
}

La solita idea è usare la ricorsione in fase di compilazione. In effetti, questa idea viene utilizzata per creare un printf che sia sicuro per i tipi, come indicato nelle carte tuple originali.

Questo può essere facilmente generalizzato in for_eachtuple for:

#include <tuple>
#include <utility> 

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...> &, FuncT) // Unused arguments are given no names.
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...>& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
  }

Sebbene ciò richieda un certo sforzo per FuncTrappresentare qualcosa con gli overload appropriati per ogni tipo che la tupla potrebbe contenere. Funziona meglio se sai che tutti gli elementi della tupla condivideranno una classe base comune o qualcosa di simile.


5
Grazie per il bel esempio semplice. Per i principianti di C ++ che cercano informazioni su come funziona, vedere SFINAE e la enable_ifdocumentazione .
Faheem Mitha

Questo potrebbe essere facilmente generalizzato come generico for_each. In effetti, l'ho fatto da solo. :-) Penso che questa risposta sarebbe più utile se fosse già generalizzata.
Omnifario

4
Lì ho aggiunto la generalizzazione perché ne avevo effettivamente bisogno e penso che sarebbe utile che gli altri lo vedessero.
Omnifario

2
Nota: potresti anche aver bisogno di versioni con const std::tuple<Tp...>&.. Se non intendi modificare le tuple durante l'iterazione, quelle constversioni saranno sufficienti.
chitarra letale

2
Non come scritto .. Potresti creare una versione con l'indicizzazione capovolta - inizia con I = sizeof ... (Tp) e fai il conto alla rovescia. Quindi fornire un numero massimo di argomenti in modo esplicito. Puoi anche creare una versione che non funziona con un tipo di tag, ad esempio break_t. Quindi metteresti un oggetto di quel tipo di tag nella tua tupla quando volevi interrompere la stampa. Oppure puoi fornire un tipo di stop come modello parm. Ovviamente non potresti rompere in fase di esecuzione.
emsr

55

In C ++ 17, puoi usare std::applycon fold expression :

std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_tuple);

Un esempio completo per la stampa di una tupla:

#include <tuple>
#include <iostream>

int main()
{
    std::tuple t{42, 'a', 4.2}; // Another C++17 feature: class template argument deduction
    std::apply([](auto&&... args) {((std::cout << args << '\n'), ...);}, t);
}

[Esempio online su Coliru]

Questa soluzione risolve il problema dell'ordine di valutazione nella risposta di M. Alaggan .


1
Potresti spiegare cosa sta succedendo qui ((std::cout << args << '\n'), ...);:? Il lambda viene invocato una volta con gli elementi della tupla scompattati come args, ma che succede con le doppie parentesi?
helmesjo

4
@helmesjo Si espande in un'espressione virgola ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n'))qui.
xskxzr

Nota che nel caso in cui desideri fare cose che non sono legali in un'espressione virgola (come la dichiarazione di variabili e blocchi), puoi mettere tutto ciò in un metodo e chiamarlo semplicemente dall'interno dell'espressione virgola piegata.
Miral

24

In C ++ 17 puoi fare questo:

std::apply([](auto ...x){std::make_tuple(x.do_something()...);} , the_tuple);

Funziona già in Clang ++ 3.9, usando std :: experiment :: apply.


4
Questo non porta all'iterazione - cioè alle chiamate di do_something()- che si verificano in un ordine non specificato, perché il pacchetto di parametri viene espanso all'interno di una chiamata di funzione (), in cui gli argomenti hanno un ordine non specificato? Potrebbe essere molto significativo; Immagino che la maggior parte delle persone si aspetterebbe che l'ordinamento sia garantito nello stesso ordine dei membri, cioè degli indici std::get<>(). AFAIK, per ottenere un ordine garantito in casi come questo, l'espansione deve essere eseguita entro {braces}. Ho sbagliato? Questa risposta pone l'accento su tale ordinamento: stackoverflow.com/a/16387374/2757035
underscore_d

21

Usa Boost.Hana e lambda generici:

#include <tuple>
#include <iostream>
#include <boost/hana.hpp>
#include <boost/hana/ext/std/tuple.hpp>

struct Foo1 {
    int foo() const { return 42; }
};

struct Foo2 {
    int bar = 0;
    int foo() { bar = 24; return bar; }
};

int main() {
    using namespace std;
    using boost::hana::for_each;

    Foo1 foo1;
    Foo2 foo2;

    for_each(tie(foo1, foo2), [](auto &foo) {
        cout << foo.foo() << endl;
    });

    cout << "foo2.bar after mutation: " << foo2.bar << endl;
}

http://coliru.stacked-crooked.com/a/27b3691f55caf271


4
Per favore, per favore, per favore, non andare using namespace boost::fusion(specialmente insieme a using namespace std). Ora non c'è modo di sapere se for_eachè std::for_eachoboost::fusion::for_each
Bulletmagnet

3
@ Bulletmagnet questo è stato fatto qui per lacune e ADL può gestirlo senza problemi. Inoltre, funziona anche in locale.
pepper_chico

16

C ++ sta introducendo istruzioni di espansione per questo scopo. Inizialmente erano sulla buona strada per C ++ 20 ma hanno mancato di poco il taglio a causa della mancanza di tempo per la revisione della formulazione della lingua (vedere qui e qui ).

La sintassi attualmente concordata (vedere i collegamenti sopra) è:

{
    auto tup = std::make_tuple(0, 'a', 3.14);
    template for (auto elem : tup)
        std::cout << elem << std::endl;
}

15

Un modo più semplice, intuitivo e compatibile con il compilatore di farlo in C ++ 17, utilizzando if constexpr:

// prints every element of a tuple
template<size_t I = 0, typename... Tp>
void print(std::tuple<Tp...>& t) {
    std::cout << std::get<I>(t) << " ";
    // do things
    if constexpr(I+1 != sizeof...(Tp))
        print<I+1>(t);
}

Questa è la ricorsione in fase di compilazione, simile a quella presentata da @emsr. Ma questo non usa SFINAE quindi (penso) è più adatto al compilatore.


8

È necessario utilizzare la metaprogrammazione dei modelli, qui mostrata con Boost.Tuple:

#include <boost/tuple/tuple.hpp>
#include <iostream>

template <typename T_Tuple, size_t size>
struct print_tuple_helper {
    static std::ostream & print( std::ostream & s, const T_Tuple & t ) {
        return print_tuple_helper<T_Tuple,size-1>::print( s, t ) << boost::get<size-1>( t );
    }
};

template <typename T_Tuple>
struct print_tuple_helper<T_Tuple,0> {
    static std::ostream & print( std::ostream & s, const T_Tuple & ) {
        return s;
    }
};

template <typename T_Tuple>
std::ostream & print_tuple( std::ostream & s, const T_Tuple & t ) {
    return print_tuple_helper<T_Tuple,boost::tuples::length<T_Tuple>::value>::print( s, t );
}

int main() {

    const boost::tuple<int,char,float,char,double> t( 0, ' ', 2.5f, '\n', 3.1416 );
    print_tuple( std::cout, t );

    return 0;
}

In C ++ 0x, puoi invece scrivere print_tuple()come funzione di modello variadico.


8

Per prima cosa definisci alcuni helper dell'indice:

template <size_t ...I>
struct index_sequence {};

template <size_t N, size_t ...I>
struct make_index_sequence : public make_index_sequence<N - 1, N - 1, I...> {};

template <size_t ...I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};

Con la tua funzione vorresti applicare su ogni elemento della tupla:

template <typename T>
/* ... */ foo(T t) { /* ... */ }

tu puoi scrivere:

template<typename ...T, size_t ...I>
/* ... */ do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) {
    std::tie(foo(std::get<I>(ts)) ...);
}

template <typename ...T>
/* ... */ do_foo(std::tuple<T...> &ts) {
    return do_foo_helper(ts, make_index_sequence<sizeof...(T)>());
}

O se fooritorni void, usa

std::tie((foo(std::get<I>(ts)), 1) ... );

Nota: in C ++ 14 make_index_sequenceè già definito ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Se hai bisogno di un ordine di valutazione da sinistra a destra, considera qualcosa del genere:

template <typename T, typename ...R>
void do_foo_iter(T t, R ...r) {
    foo(t);
    do_foo(r...);
}

void do_foo_iter() {}

template<typename ...T, size_t ...I>
void do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) {
    do_foo_iter(std::get<I>(ts) ...);
}

template <typename ...T>
void do_foo(std::tuple<T...> &ts) {
    do_foo_helper(ts, make_index_sequence<sizeof...(T)>());
}

1
Dovrebbe fooeseguire il cast del valore restituito da a voidprima di invocare operator,per evitare un possibile sovraccarico dell'operatore patologico.
Yakk - Adam Nevraumont

7

Ecco un modo semplice in C ++ 17 per iterare su elementi tuple con la sola libreria standard:

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
        std::tuple_size_v<
            std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
>
void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)
{
    if constexpr (Index < Size)
    {
        std::invoke(callable, args..., std::get<Index>(tuple));

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    }
}

Esempio:

#include <iostream>

int main()
{
    std::tuple<int, char> items{1, 'a'};
    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
    });
}

Produzione:

1
a

Questo può essere esteso per interrompere in modo condizionale il ciclo nel caso in cui il callable restituisca un valore (ma funziona ancora con i callables che non restituiscono un valore assegnabile bool, ad esempio void):

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
    std::tuple_size_v<
    std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
    >
    void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)
{
    if constexpr (Index < Size)
    {
        if constexpr (std::is_assignable_v<bool&, std::invoke_result_t<TCallable&&, TArgs&&..., decltype(std::get<Index>(tuple))>>)
        {
            if (!std::invoke(callable, args..., std::get<Index>(tuple)))
                return;
        }
        else
        {
            std::invoke(callable, args..., std::get<Index>(tuple));
        }

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    }
}

Esempio:

#include <iostream>

int main()
{
    std::tuple<int, char> items{ 1, 'a' };
    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
    });

    std::cout << "---\n";

    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
        return false;
    });
}

Produzione:

1
a
---
1

5

Se vuoi usare std :: tuple e hai un compilatore C ++ che supporta modelli variadici, prova il codice qui sotto (testato con g ++ 4.5). Questa dovrebbe essere la risposta alla tua domanda.

#include <tuple>

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

// ----------- FOR EACH -----------------
template<typename Func, typename Last>
void for_each_impl(Func&& f, Last&& last)
{
    f(last);
}

template<typename Func, typename First, typename ... Rest>
void for_each_impl(Func&& f, First&& first, Rest&&...rest) 
{
    f(first);
    for_each_impl( std::forward<Func>(f), rest...);
}

template<typename Func, int ... Indexes, typename ... Args>
void for_each_helper( Func&& f, index_tuple<Indexes...>, std::tuple<Args...>&& tup)
{
    for_each_impl( std::forward<Func>(f), std::forward<Args>(std::get<Indexes>(tup))...);
}

template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>& tup, Func&& f)
{
   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );
}

template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>&& tup, Func&& f)
{
   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );
}

boost :: fusion è un'altra opzione, ma richiede il proprio tipo di tupla: boost :: fusion :: tuple. È meglio attenersi allo standard! Ecco un test:

#include <iostream>

// ---------- FUNCTOR ----------
struct Functor 
{
    template<typename T>
    void operator()(T& t) const { std::cout << t << std::endl; }
};

int main()
{
    for_each( std::make_tuple(2, 0.6, 'c'), Functor() );
    return 0;
}

il potere dei modelli variadici!


Ho provato la tua prima soluzione, ma non riesce con questa funzione sulle coppie. Qualche idea del perché? Template <typename T, typename U> void addt (pair <T, U> p) {cout << p.first + p.second << endl; } int main (int argc, char * argv []) {cout << "Hello." << endl; for_each (make_tuple (2,3,4), [] (int i) {cout << i << endl;}); for_each (make_tuple (make_pair (1,2), make_pair (3,4)), addt); return 0; }
user2023370

È un peccato che questa risposta sia scritta in modo così prolisso perché penso che il modo di iterare (for_each_impl) sia la più elegante di tutte le soluzioni che ho visto.
joki

3

In MSVC STL c'è una funzione _For_each_tuple_element (non documentata):

#include <tuple>

// ...

std::tuple<int, char, float> values{};
std::_For_each_tuple_element(values, [](auto&& value)
{
    // process 'value'
});

2

Altri hanno menzionato alcune librerie di terze parti ben progettate a cui potresti rivolgerti. Tuttavia, se stai usando C ++ senza queste librerie di terze parti, il codice seguente può essere d'aiuto.

namespace detail {

template <class Tuple, std::size_t I, class = void>
struct for_each_in_tuple_helper {
  template <class UnaryFunction>
  static void apply(Tuple&& tp, UnaryFunction& f) {
    f(std::get<I>(std::forward<Tuple>(tp)));
    for_each_in_tuple_helper<Tuple, I + 1u>::apply(std::forward<Tuple>(tp), f);
  }
};

template <class Tuple, std::size_t I>
struct for_each_in_tuple_helper<Tuple, I, typename std::enable_if<
    I == std::tuple_size<typename std::decay<Tuple>::type>::value>::type> {
  template <class UnaryFunction>
  static void apply(Tuple&&, UnaryFunction&) {}
};

}  // namespace detail

template <class Tuple, class UnaryFunction>
UnaryFunction for_each_in_tuple(Tuple&& tp, UnaryFunction f) {
  detail::for_each_in_tuple_helper<Tuple, 0u>
      ::apply(std::forward<Tuple>(tp), f);
  return std::move(f);
}

Nota: il codice viene compilato con qualsiasi compilatore che supporta C ++ 11 e mantiene la coerenza con il design della libreria standard:

  1. La tupla non deve essere necessariamente std::tuple, e invece può essere qualsiasi cosa che supporti std::gete std::tuple_size; in particolare, std::arraye std::pairpossono essere utilizzati;

  2. La tupla può essere un tipo di riferimento o qualificata per cv;

  3. Ha un comportamento simile a std::for_eache restituisce l'input UnaryFunction;

  4. Per gli utenti C ++ 14 (o versione laster) typename std::enable_if<T>::typee typename std::decay<T>::typepotrebbe essere sostituito con la loro versione semplificata std::enable_if_t<T>estd::decay_t<T> ;

  5. Per gli utenti C ++ 17 (Laster versione o), std::tuple_size<T>::valuepotrebbe essere sostituito con la sua versione semplificata, std::tuple_size_v<T>.

  6. Per gli utenti di C ++ 20 (o versione laster), la SFINAEfunzionalità potrebbe essere implementata con l' estensione Concepts.


2

Usare constexpre if constexpr(C ++ 17) è abbastanza semplice e diretto:

template <std::size_t I = 0, typename ... Ts>
void print(std::tuple<Ts...> tup) {
  if constexpr (I == sizeof...(Ts)) {
    return;
  } else {
    std::cout << std::get<I>(tup) << ' ';
    print<I+1>(tup);
  }
}

1

Potrei aver perso questo treno, ma sarà qui per riferimento futuro.
Ecco il mio costrutto basato su questa risposta e su questo succo :

#include <tuple>
#include <utility>

template<std::size_t N>
struct tuple_functor
{
    template<typename T, typename F>
    static void run(std::size_t i, T&& t, F&& f)
    {
        const std::size_t I = (N - 1);
        switch(i)
        {
        case I:
            std::forward<F>(f)(std::get<I>(std::forward<T>(t)));
            break;

        default:
            tuple_functor<I>::run(i, std::forward<T>(t), std::forward<F>(f));
        }
    }
};

template<>
struct tuple_functor<0>
{
    template<typename T, typename F>
    static void run(std::size_t, T, F){}
};

Quindi lo usi come segue:

template<typename... T>
void logger(std::string format, T... args) //behaves like C#'s String.Format()
{
    auto tp = std::forward_as_tuple(args...);
    auto fc = [](const auto& t){std::cout << t;};

    /* ... */

    std::size_t some_index = ...
    tuple_functor<sizeof...(T)>::run(some_index, tp, fc);

    /* ... */
}

Potrebbe esserci spazio per miglioramenti.


Secondo il codice di OP, diventerebbe questo:

const std::size_t num = sizeof...(T);
auto my_tuple = std::forward_as_tuple(t...);
auto do_sth = [](const auto& elem){/* ... */};
for(int i = 0; i < num; ++i)
    tuple_functor<num>::run(i, my_tuple, do_sth);

1

Di tutte le risposte che ho visto qui, qui e qui , mi è piaciuto di più il modo di iterare di @sigidagi. Sfortunatamente, la sua risposta è molto prolissa che a mio parere oscura la chiarezza intrinseca.

Questa è la mia versione della sua soluzione che è più concisa e funziona con std::tuple, std::paire std::array.

template<typename UnaryFunction>
void invoke_with_arg(UnaryFunction)
{}

/**
 * Invoke the unary function with each of the arguments in turn.
 */
template<typename UnaryFunction, typename Arg0, typename... Args>
void invoke_with_arg(UnaryFunction f, Arg0&& a0, Args&&... as)
{
    f(std::forward<Arg0>(a0));
    invoke_with_arg(std::move(f), std::forward<Args>(as)...);
}

template<typename Tuple, typename UnaryFunction, std::size_t... Indices>
void for_each_helper(Tuple&& t, UnaryFunction f, std::index_sequence<Indices...>)
{
    using std::get;
    invoke_with_arg(std::move(f), get<Indices>(std::forward<Tuple>(t))...);
}

/**
 * Invoke the unary function for each of the elements of the tuple.
 */
template<typename Tuple, typename UnaryFunction>
void for_each(Tuple&& t, UnaryFunction f)
{
    using size = std::tuple_size<typename std::remove_reference<Tuple>::type>;
    for_each_helper(
        std::forward<Tuple>(t),
        std::move(f),
        std::make_index_sequence<size::value>()
    );
}

Demo: coliru

C ++ 14 std::make_index_sequencepossono essere implementati per C ++ 11 .


0

La tupla di boost fornisce funzioni di supporto get_head()e get_tail()quindi le funzioni di supporto potrebbero assomigliare a questo:

inline void call_do_sth(const null_type&) {};

template <class H, class T>
inline void call_do_sth(cons<H, T>& x) { x.get_head().do_sth(); call_do_sth(x.get_tail()); }

come descritto qui http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

con std::tupleesso dovrebbe essere simile.

In realtà, sfortunatamente std::tuplenon sembra fornire tale interfaccia, quindi i metodi suggeriti prima dovrebbero funzionare, oppure dovresti passare a quelli boost::tupleche hanno altri vantaggi (come gli operatori io già forniti). Sebbene ci sia uno svantaggio di boost::tuplegcc: non accetta ancora modelli variadici, ma potrebbe essere già stato risolto poiché non ho l'ultima versione di boost installata sulla mia macchina.


0

Mi sono imbattuto nello stesso problema per l'iterazione su una tupla di oggetti funzione, quindi ecco un'altra soluzione:

#include <tuple> 
#include <iostream>

// Function objects
class A 
{
    public: 
        inline void operator()() const { std::cout << "A\n"; };
};

class B 
{
    public: 
        inline void operator()() const { std::cout << "B\n"; };
};

class C 
{
    public:
        inline void operator()() const { std::cout << "C\n"; };
};

class D 
{
    public:
        inline void operator()() const { std::cout << "D\n"; };
};


// Call iterator using recursion.
template<typename Fobjects, int N = 0> 
struct call_functors 
{
    static void apply(Fobjects const& funcs)
    {
        std::get<N>(funcs)(); 

        // Choose either the stopper or descend further,  
        // depending if N + 1 < size of the tuple. 
        using caller = std::conditional_t
        <
            N + 1 < std::tuple_size_v<Fobjects>,
            call_functors<Fobjects, N + 1>, 
            call_functors<Fobjects, -1>
        >;

        caller::apply(funcs); 
    }
};

// Stopper.
template<typename Fobjects> 
struct call_functors<Fobjects, -1>
{
    static void apply(Fobjects const& funcs)
    {
    }
};

// Call dispatch function.
template<typename Fobjects>
void call(Fobjects const& funcs)
{
    call_functors<Fobjects>::apply(funcs);
};


using namespace std; 

int main()
{
    using Tuple = tuple<A,B,C,D>; 

    Tuple functors = {A{}, B{}, C{}, D{}}; 

    call(functors); 

    return 0; 
}

Produzione:

A 
B 
C 
D

0

Un'altra opzione sarebbe implementare gli iteratori per le tuple. Questo ha il vantaggio di poter utilizzare una varietà di algoritmi forniti dalla libreria standard e basati su intervalli per i cicli. Un approccio elegante a questo è spiegato qui https://foonathan.net/2017/03/tuple-iterator/ . L'idea di base è di trasformare tuple in un intervallo con begin()e end()metodi per fornire iteratori. Lo stesso iteratore restituisce un std::variant<...>che può essere visitato utilizzando std::visit.

Alcuni esempi:

auto t = std::tuple{ 1, 2.f, 3.0 };
auto r = to_range(t);

for(auto v : r)
{
    std::visit(unwrap([](auto& x)
        {
            x = 1;
        }), v);
}

std::for_each(begin(r), end(r), [](auto v)
    {
        std::visit(unwrap([](auto& x)
            {
                x = 0;
            }), v);
    });

std::accumulate(begin(r), end(r), 0.0, [](auto acc, auto v)
    {
        return acc + std::visit(unwrap([](auto& x)
        {
            return static_cast<double>(x);
        }), v);
    });

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(unwrap([](const auto& x)
        {
            std::cout << x << std::endl;
        }), v);
});

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(overload(
        [](int x) { std::cout << "int" << std::endl; },
        [](float x) { std::cout << "float" << std::endl; },
        [](double x) { std::cout << "double" << std::endl; }), v);
});

La mia implementazione (che è fortemente basata sulle spiegazioni nel link sopra):

#ifndef TUPLE_RANGE_H
#define TUPLE_RANGE_H

#include <utility>
#include <functional>
#include <variant>
#include <type_traits>

template<typename Accessor>
class tuple_iterator
{
public:
    tuple_iterator(Accessor acc, const int idx)
        : acc_(acc), index_(idx)
    {

    }

    tuple_iterator operator++()
    {
        ++index_;
        return *this;
    }

    template<typename T>
    bool operator ==(tuple_iterator<T> other)
    {
        return index_ == other.index();
    }

    template<typename T>
    bool operator !=(tuple_iterator<T> other)
    {
        return index_ != other.index();
    }

    auto operator*() { return std::invoke(acc_, index_); }

    [[nodiscard]] int index() const { return index_; }

private:
    const Accessor acc_;
    int index_;
};

template<bool IsConst, typename...Ts>
struct tuple_access
{
    using tuple_type = std::tuple<Ts...>;
    using tuple_ref = std::conditional_t<IsConst, const tuple_type&, tuple_type&>;

    template<typename T>
    using element_ref = std::conditional_t<IsConst,
        std::reference_wrapper<const T>,
        std::reference_wrapper<T>>;

    using variant_type = std::variant<element_ref<Ts>...>;
    using function_type = variant_type(*)(tuple_ref);
    using table_type = std::array<function_type, sizeof...(Ts)>;

private:
    template<size_t Index>
    static constexpr function_type create_accessor()
    {
        return { [](tuple_ref t) -> variant_type
        {
            if constexpr (IsConst)
                return std::cref(std::get<Index>(t));
            else
                return std::ref(std::get<Index>(t));
        } };
    }

    template<size_t...Is>
    static constexpr table_type create_table(std::index_sequence<Is...>)
    {
        return { create_accessor<Is>()... };
    }

public:
    static constexpr auto table = create_table(std::make_index_sequence<sizeof...(Ts)>{}); 
};

template<bool IsConst, typename...Ts>
class tuple_range
{
public:
    using tuple_access_type = tuple_access<IsConst, Ts...>;
    using tuple_ref = typename tuple_access_type::tuple_ref;

    static constexpr auto tuple_size = sizeof...(Ts);

    explicit tuple_range(tuple_ref tuple)
        : tuple_(tuple)
    {
    }

    [[nodiscard]] auto begin() const 
    { 
        return tuple_iterator{ create_accessor(), 0 };
    }

    [[nodiscard]] auto end() const 
    { 
        return tuple_iterator{ create_accessor(), tuple_size };
    }

private:
    tuple_ref tuple_;

    auto create_accessor() const
    { 
        return [this](int idx)
        {
            return std::invoke(tuple_access_type::table[idx], tuple_);
        };
    }
};

template<bool IsConst, typename...Ts>
auto begin(const tuple_range<IsConst, Ts...>& r)
{
    return r.begin();
}

template<bool IsConst, typename...Ts>
auto end(const tuple_range<IsConst, Ts...>& r)
{
    return r.end();
}

template <class ... Fs>
struct overload : Fs... {
    explicit overload(Fs&&... fs) : Fs{ fs }... {}
    using Fs::operator()...;

    template<class T>
    auto operator()(std::reference_wrapper<T> ref)
    {
        return (*this)(ref.get());
    }

    template<class T>
    auto operator()(std::reference_wrapper<const T> ref)
    {
        return (*this)(ref.get());
    }
};

template <class F>
struct unwrap : overload<F>
{
    explicit unwrap(F&& f) : overload<F>{ std::forward<F>(f) } {}
    using overload<F>::operator();
};

template<typename...Ts>
auto to_range(std::tuple<Ts...>& t)
{
    return tuple_range<false, Ts...>{t};
}

template<typename...Ts>
auto to_range(const std::tuple<Ts...>& t)
{
    return tuple_range<true, Ts...>{t};
}


#endif

L'accesso in sola lettura è supportato anche passando un const std::tuple<>&a to_range().


0

Espandendo la risposta @Stypox, possiamo rendere la loro soluzione più generica (da C ++ 17 in poi). Aggiungendo un argomento di funzione richiamabile:

template<size_t I = 0, typename... Tp, typename F>
void for_each_apply(std::tuple<Tp...>& t, F &&f) {
    f(std::get<I>(t));
    if constexpr(I+1 != sizeof...(Tp)) {
        for_each_apply<I+1>(t, std::forward<F>(f));
    }
}

Quindi, abbiamo bisogno di una strategia per visitare ogni tipo.

Cominciamo con alcuni helper (i primi due presi da cppreference):

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class ... Ts> struct variant_ref { using type = std::variant<std::reference_wrapper<Ts>...>; };

variant_ref viene utilizzato per consentire la modifica dello stato delle tuple.

Uso:

std::tuple<Foo, Bar, Foo> tuples;

for_each_apply(tuples,
               [](variant_ref<Foo, Bar>::type &&v) {
                   std::visit(overloaded {
                       [](Foo &arg) { arg.foo(); },
                       [](Bar const &arg) { arg.bar(); },
                   }, v);
               });

Risultato:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Per completezza, ecco i miei Bar& Foo:

struct Foo {
    void foo() {std::cout << "Foo" << i++ << std::endl;}
    int i = 0;
};
struct Bar {
    void bar() const {std::cout << "Bar" << std::endl;}
};
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.