web-dev-qa-db-fra.com

Comment pouvez-vous parcourir les éléments d'un std :: tuple?

Comment puis-je parcourir un tuple (à l'aide de C++ 11)? J'ai essayé ce qui suit:

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

mais ça ne marche pas:

Erreur 1: désolé, non implémenté: impossible de développer ‘Listener ...’ dans une liste d’arguments de longueur fixe.
Erreur 2: je ne peux pas apparaître dans une expression constante.

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

74
1521237

Boost.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
Éric Malenfant

J'ai une réponse basée sur Itérant 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écursion du temps de compilation. En fait, cette idée est utilisée pour créer un printf de type sûr, comme indiqué dans les papiers Tuple d'origine.

Ceci peut être facilement généralisé dans 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 alors quelques efforts 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 que tous les éléments Tuple partageront une classe de base commune ou quelque chose de similaire.

113
emsr

Utilisez Boost.Hana et les 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

20
pepper_chico

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 :: experimental :: apply.

15
M. Alaggan

Vous devez utiliser la métaprogrammation des modèles, illustré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 plutôt écrire print_Tuple() en tant que fonction de modèle variadique.

9
Marc Mutz - mmutz

C++ introduit instructions d'expansion à cette fin. Ils étaient initialement sur la bonne voie pour C++ 20, mais ont raté la coupe de près, faute de temps pour revoir leur formulation (voir ici et ici ).

La syntaxe actuellement acceptée (voir les liens ci-dessus) est la suivante:

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

En C++ 17, vous pouvez utiliser std::apply avec expression de pli :

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

Un exemple complet pour imprimer un tuple:

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

[Exemple en ligne sur Coliru]

Cette solution résout le problème de l'ordre d'évaluation dans La réponse de M. Alaggan .

8
xskxzr

Commencez par définir 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 du tuple:

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

tu peux é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: En C++ 14, make_index_sequence est déjà défini ( http://fr.cppreference.com/w/cpp/utility/integer_sequence ).

Si vous avez besoin d'un ordre d'évaluation de gauche à droite, envisagez 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
user1447257

Si vous voulez utiliser std :: Tuple et que vous avez un compilateur C++ qui supporte 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
sigidagi

Voici un moyen facile d'itérer C++ 17 sur des éléments Tuple à l'aide d'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>

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

Sortie:

1
a

Cela peut être étendu pour rompre la boucle de manière conditionnelle si l'appelable renvoie une valeur (mais fonctionne toujours avec les appelables qui ne renvoient pas de valeur assignable, par exemple 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)...);
    }
}

Exemple:

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

Sortie:

1
a
---
1
3
Marcin Zawiejski

boost's Tuple fournit les fonctions d'aide get_head() et get_tail() afin que vos fonctions d'assistance ressemblent à 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()); }

comme 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 réalité, malheureusement, std::Tuple ne semble pas fournir cette interface, les méthodes suggérées auparavant devraient donc fonctionner, sinon vous devrez passer à boost::Tuple, qui présente d'autres avantages (comme les opérateurs io déjà fournis). 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 déjà être résolu car je n'ai pas la dernière version de boost installée sur ma machine.

1
Slava

Une façon plus simple, intuitive et conviviale de le faire en C++ 17, en utilisant 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);
}

C’est une récursion au moment de la compilation, similaire à celle présentée par @emsr. Mais cela n’utilise pas SFINAE, donc (je pense) c’est plus convivial pour le compilateur.

1
Stypox

J'ai peut-être manqué ce train, mais ce sera là pour référence future.
Voici ma construction basée sur cette réponse et sur ceci 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 pour des améliorations.


Selon le code de l'OP, cela deviendrait:

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
bit2shift

Dans MSVC STL, il existe 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'
});
1
Marcin Zawiejski

En utilisant constexpr et if constexpr (C++ 17), cela est assez simple et direct:

_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
Andreas DM

De toutes les réponses que j'ai vues ici, ici et ici , j'ai bien aimé @sigidagi la meilleure façon d'itérer Malheureusement, sa réponse est très verbeuse, ce qui à mon avis masque la clarté inhérente.

Ceci est ma version de sa solution qui est plus concise 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émo: coliru

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

0
joki

D'autres ont mentionné des bibliothèques tierces bien conçues que vous pouvez consulter. Toutefois, si vous utilisez C++ sans ces bibliothèques tierces, le code suivant peut vous aider.

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

Remarque: Le code est compilé avec tout compilateur prenant en charge C++ 11 et conserve la cohérence avec la conception de la bibliothèque standard:

  1. Le tuple ne doit pas nécessairement être std::Tuple, mais peut contenir tout ce qui prend en charge std::get et std::Tuple_size; en particulier, std::array et std::pair peuvent être utilisés;

  2. Le tuple peut être un type de référence ou qualifié cv;

  3. Son comportement est similaire à celui de std::for_each et renvoie l'entrée UnaryFunction;

  4. Pour les utilisateurs de C++ 14 (ou version plus récente), typename std::enable_if<T>::type et typename std::decay<T>::type peuvent être remplacés par leur version simplifiée, std::enable_if_t<T> et std::decay_t<T>;

  5. Pour les utilisateurs de C++ 17 (ou version plus récente), std::Tuple_size<T>::value pourrait être remplacé par sa version simplifiée, std::Tuple_size_v<T>.

  6. Pour les utilisateurs de C++ 20 (ou version plus récente), la fonctionnalité SFINAE pourrait être implémentée avec Concepts.

0
M. W.

Je suis tombé sur le même problème pour effectuer une itération sur un tuple d'objets fonction.

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

Sortie: 

A 
B 
C 
D
0
tmaric