itérer sur tuple

Comment puis-je itérer sur un tuple (en utilisant C++11)? J'ai essayé ce qui suit, mais cela ne fonctionne pas:

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

Erreur 1: désolé, non implémenté: impossible de développer L'écouteur ...'dans une liste d'arguments de longueur fixe.
Erreur 2: Je ne peux pas apparaître dans une expression constante.

Alors, comment puis-je itérer correctement sur les éléments d'un tuple?

66
demandé sur Xeo 2009-07-29 09:57:22

12 réponses

Coup de pouce.Fusion est une possibilité:

Exemple non testé:

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

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
21
répondu Éric Malenfant 2012-02-13 18:51:20

J'ai une réponse basée sur Itération sur un Tuple:

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

L'idée habituelle est d'utiliser la récursivité à la compilation. En fait, cette idée est utilisée pour faire un printf qui est de type sûr comme indiqué dans les papiers tuple originaux.

Cela peut être facilement généralisé en un for_each pour les tuples:

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

Bien que cela nécessite un certain effort pour que FuncT représente quelque chose avec les surcharges appropriées pour chaque type que le tuple peut contenir. Cela fonctionne mieux si vous savez tout les éléments tuple partageront une classe de base commune ou quelque chose de similaire.

102
répondu emsr 2017-05-23 12:03:05

Utilisez Boost.Hana et lambdas génériques:

#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

19
répondu pepper_chico 2017-06-15 05:36:23

En C++17 vous pouvez faire ceci:

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

Cela fonctionne déjà dans Clang++ 3.9, en utilisant std::expérimental::appliquer.

14
répondu M. Alaggan 2016-05-08 13:05:23

Vous devez utiliser la métaprogrammation de modèle, montrée ici avec 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;
}

En C++0x, vous pouvez écrire {[1] } en tant que fonction de modèle variadique à la place.

9
répondu Marc Mutz - mmutz 2009-07-29 06:54:25

Définissez D'abord quelques aides d'index:

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

Avec votre fonction, vous souhaitez appliquer sur chaque élément tuple:

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

, Vous pouvez écrire:

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

Ou si foo renvoie void, utilisez

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

Remarque: Sur le C++14 make_index_sequence est déjà défini (http://en.cppreference.com/w/cpp/utility/integer_sequence).

Si vous avez besoin d'un ordre d'évaluation de gauche à droite, considérez quelque chose comme ceci:

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)>());
}
6
répondu user1447257 2014-11-04 14:21:19

Si vous voulez utiliser std:: tuple et que vous avez un compilateur C++ qui prend en charge les modèles variadiques, essayez le code ci-dessous (testé avec G++4.5). Cela devrait être la réponse à votre question.

#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 est une autre option, mais elle nécessite son propre type de tuple: boost::fusion:: tuple. Permet de mieux coller à la norme! Voici 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;
}

La puissance des modèles variadiques!

5
répondu sigidagi 2011-06-19 12:35:58

Le tuple de Boost fournit des fonctions d'assistance get_head() et get_tail() de sorte que vos fonctions d'assistance peuvent ressembler à ceci:

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

Tel Que décrit ici http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

Avec std::tuple, il devrait être similaire.

En fait, malheureusement std::tuple ne semble pas fournir une telle interface, donc les méthodes suggérées auparavant devraient fonctionner, ou vous devriez passer à boost::tuple qui a d'autres avantages (comme les opérateurs d'E / S déjà fourni). Bien qu'il y ait un inconvénient de boost::tuple avec gcc - il n'accepte pas encore les modèles variadiques, mais cela peut être déjà corrigé car je n'ai pas la dernière version de boost installée sur ma machine.

1
répondu Slava 2011-12-14 14:29:33

J'ai peut-être raté ce train, mais ce sera ici pour référence future.
Voici mon construire sur cette base réponse et sur ce gist:

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

Vous l'utilisez ensuite comme suit:

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

    /* ... */
}

Il pourrait y avoir place à des améliorations.


Selon le code D'OP, il deviendrait ceci:

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
répondu bit2shift 2017-05-23 12:26:09

Voici une façon facile C++17 d'itérer sur des éléments tuple avec juste une bibliothèque 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)...);
    }
}

Exemple:

#include <iostream>

std::tuple<int, char> items;
for_each(items, [](const auto& item) {
    std::cout << item << "\n";
});
1
répondu Marcin Zawiejski 2017-12-11 22:27:05

Dans MSVC STL il y a une fonction _For_each_tuple_element (non documentée):

#include <tuple>

// ...

std::tuple<int, char, float> values{};
std::_For_each_tuple_element(values, [](auto&& value)
{
    // process 'value'
});
0
répondu Marcin Zawiejski 2017-12-29 04:41:27

De toutes les réponses que j'ai vu ici, ici et ici, j'ai aimé @sigidagi's en voie d'itération des meilleurs. Malheureusement, sa réponse est très détaillé qui, à mon avis obscurcit la clarté inhérente.

C'est ma version de sa solution, qui est plus concis et fonctionne avec std::tuple, std::pair et 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>()
    );
}

Démonstration: coliru

C++14 std::make_index_sequence peut être implémenté pour C++11 .

0
répondu joki 2018-04-24 11:50:24