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


112

Comment puis-je parcourir un tuple (en utilisant 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 cela ne fonctionne 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?


2
Puis-je vous demander comment compilez-vous en C ++ 0x? Il n'est pas publié ni prêt pour autant que je sache.
Burkhard

5
g ++ contient un support expérimental de certaines fonctionnalités C ++ 0X, y compris les modèles variadiques, depuis la version 4.3. D'autres compilateurs font de même (avec des ensembles de fonctionnalités différents, si vous voulez les utiliser en production, vous êtes de retour dans les années 90 avec une large variation de support pour des choses de pointe)
AProgrammer

J'utilise la version 4.4 de g ++ avec std = c ++ 0x

9
Cette question nécessite une mise à jour C ++ 11.
Omnifarious

2
@Omnifarious maintenant, il a besoin d'une mise à jour C ++ 14
pepper_chico

Réponses:


26

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

@ViktorSehr AFAICT ce n'est pas le cas (du moins sur GCC 4.7.2)? Quelqu'un avec un indice?
sehe

@ViktorSehr trouvé le problème: un bug / omission provoque le comportement de la fusion à dépendre de l'ordre du comprend, voir billet Boost # 8418 pour plus de détails
sehe

besoin d'utiliser boost :: fusion :: tuple au lieu de std :: tuple pour que cela fonctionne.
Marcin

Sous GCC 8.1 / mingw-64, j'obtiens deux avertissements pour l'utilisation de boost :: fusion :: for_each avec des expressions lambda std: boost / mpl / assert.hpp: 188: 21: avertissement: parenthèses inutiles dans la déclaration de 'assert_arg' [-Wparentheses] a échoué ************ (Pred :: ************ boost / mpl / assert.hpp: 193: 21: avertissement: parenthèses inutiles dans la déclaration de 'assert_not_arg' [-Wparentheses] a échoué ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein

129

J'ai une réponse basée sur l' 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 créer un printf dont le type est sûr, comme indiqué dans les papiers de tuple d'origine.

Cela peut être facilement généralisé en un for_eachpour 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 demande alors un certain effort pour FuncTreprésenter 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 de tuple partageront une classe de base commune ou quelque chose de similaire.


5
Merci pour le bel exemple simple. Pour les débutants en C ++ à la recherche d'informations sur son fonctionnement, consultez SFINAE et la enable_ifdocumentation .
Faheem Mitha

Cela pourrait facilement être généralisé pour être un générique for_each. En fait, je l'ai fait moi-même. :-) Je pense que cette réponse serait plus utile si elle était déjà généralisée.
Omnifarious

4
Là, j'ai ajouté la généralisation parce que j'en avais vraiment besoin d'une, et je pense que ce serait utile pour les autres de voir.
Omnifarious

2
Remarque: Vous pourriez également avoir besoin de versions avec const std::tuple<Tp...>&.. Si vous n'avez pas l'intention de modifier les tuples pendant l'itération, ces constversions suffiront.
lethal-guitar

2
Pas comme écrit .. Vous pouvez créer une version avec l'indexation inversée - commencez à I = sizeof ... (Tp) et comptez à rebours. Ensuite, fournissez explicitement un nombre maximum d'arguments. Vous pouvez également créer une version défectueuse sur un type de balise, par exemple break_t. Ensuite, vous mettriez un objet de ce type de balise dans votre tuple lorsque vous vouliez arrêter l'impression. Ou vous pouvez fournir un type d'arrêt comme paramètre de modèle. De toute évidence, vous ne pouviez pas casser au moment de l'exécution.
emsr

55

En C ++ 17, vous pouvez utiliser std::applyavec une expression fold :

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 la question de l'ordre d'évaluation dans la réponse de M. Alaggan .


1
Pourriez-vous expliquer ce qui se passe ici ((std::cout << args << '\n'), ...);:? Le lambda est appelé une fois avec les éléments tuple décompressés en tant que args, mais que se passe-t-il avec les doubles parenthèses?
helmesjo

4
@helmesjo Il se développe ici en une expression virgule ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n')).
xskxzr

Notez que si vous souhaitez faire des choses qui ne sont pas légales dans une expression de virgule (comme la déclaration de variables et de blocs), vous pouvez mettre tout cela dans une méthode et l'appeler simplement depuis l'expression de la virgule pliée.
Miral le

24

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.


4
Cela ne conduit-il pas à l'itération - c'est-à-dire aux appels de do_something()- se produisant dans un ordre non spécifié, parce que le pack de paramètres est développé dans un appel de fonction (), dans lequel les arguments ont un ordre non spécifié? Cela pourrait être très important; J'imagine que la plupart des gens s'attendraient à ce que l'ordre soit garanti dans le même ordre que les membres, c'est-à-dire que les indices std::get<>(). AFAIK, pour obtenir une commande garantie dans de tels cas, l'expansion doit être effectuée à l'intérieur {braces}. Ai-je tort? Cette réponse met l'accent sur un tel ordre: stackoverflow.com/a/16387374/2757035
underscore_d

21

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


4
Merci de ne pas y aller using namespace boost::fusion(surtout avec using namespace std). Maintenant , il n'y a aucun moyen de savoir si for_eachc'est std::for_eachouboost::fusion::for_each
Bulletmagnet

3
@Bulletmagnet cela a été fait pour la concision ici et ADL peut gérer cela sans problème. En outre, il fonctionne également localement.
pepper_chico

16

C ++ introduit des instructions d'expansion à cet effet. Ils étaient à l'origine sur la bonne voie pour C ++ 20 mais ont raté de peu la coupe en raison d'un manque de temps pour la révision du libellé de la langue (voir ici et ici ).

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

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

15

Une manière plus simple, intuitive et conviviale de faire cela 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);
}

Il s'agit de la récursivité à la compilation, similaire à celle présentée par @emsr. Mais cela n'utilise pas SFINAE donc (je pense) il est plus convivial pour le compilateur.


8

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

Dans C ++ 0x, vous pouvez écrire en print_tuple()tant que fonction de modèle variadique à la place.


8

Définissez d'abord quelques helpers 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 de 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 fooretourne void, utilisez

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

Remarque: Sur C ++ 14 make_index_sequenceest 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, 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)>());
}

1
Devrait transtyper la valeur de retour de fooà voidavant l'appel operator,pour éviter une éventuelle surcharge de l'opérateur pathologique.
Yakk - Adam Nevraumont

7

Voici un moyen simple en C ++ 17 d'itérer des éléments de 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>

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

Production:

1
a

Cela peut être étendu pour interrompre conditionnellement la boucle au cas où l'appelable renvoie une valeur (mais fonctionne toujours avec des callables qui ne retournent pas de valeur affectable booléenne, 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;
    });
}

Production:

1
a
---
1

5

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 s'en tenir à 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!


J'ai essayé votre première solution, mais elle échoue avec cette fonction sur les paires. Une idée de pourquoi? Template <typename T, typename U> void addt (pair <T, U> p) {cout << p.first + p.second << endl; } int main (int argc, char * argv []) {cout << "Bonjour." << 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

C'est dommage que cette réponse soit écrite de manière aussi verbeuse car je pense que la façon d'itérer (for_each_impl) est la plus élégante de toutes les solutions que j'ai vues.
joki

3

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

2

D'autres ont mentionné des bibliothèques tierces bien conçues vers lesquelles vous pouvez vous tourner. 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 se compile avec n'importe quel compilateur prenant en charge C ++ 11, et il reste cohérent avec la conception de la bibliothèque standard:

  1. Le tuple n'a pas besoin d'être std::tuple, et peut être tout ce qui prend en charge std::getet std::tuple_size; en particulier, std::arrayet std::pairpeut être utilisé;

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

  3. Il a le même comportement que 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>::typeet typename std::decay<T>::typepourraient être remplacés par leur version simplifiée, std::enable_if_t<T>et std::decay_t<T>;

  5. C ++ 17 (ou une version Laster) utilisateurs, std::tuple_size<T>::valuepourrait ê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 SFINAEfonctionnalité peut être implémentée avec le Concepts.


2

En utilisant constexpret if constexpr(C ++ 17), c'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

J'ai peut-être manqué ce train, mais ce sera ici pour référence future.
Voici ma construction basée sur cette réponse et sur cette substance :

#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 OP, cela 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

De toutes les réponses que je l' ai vu ici, ici et ici , je l' ai aimé @sigidagi façon de mieux de itérer. Malheureusement, sa réponse est très verbeuse, ce qui à mon avis obscurcit la clarté inhérente.

C'est ma version de sa solution qui est plus concise et fonctionne avec std::tuple, std::pairet 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

Les C ++ 14 std::make_index_sequencepeuvent être implémentés pour C ++ 11 .


0

Le tuple de boost fournit des fonctions d'assistance get_head()et get_tail()vos fonctions d'assistance peuvent donc 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()); }

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

avec std::tuplecela devrait être similaire.

En fait, malheureusement, std::tuplene semble pas fournir une telle interface, donc les méthodes suggérées auparavant devraient fonctionner, ou vous auriez besoin de passer à celle boost::tuplequi présente d'autres avantages (comme les opérateurs io déjà fournis). Bien qu'il y ait un inconvénient boost::tupleavec gcc - il n'accepte pas encore les modèles variadic, mais cela peut être déjà corrigé car je n'ai pas la dernière version de boost installée sur ma machine.


0

Je suis tombé sur le même problème pour l'itération sur un tuple d'objets de fonction, voici donc une autre solution:

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

Production:

A 
B 
C 
D

0

Une autre option serait d'implémenter des itérateurs pour les tuples. Cela présente l'avantage de pouvoir utiliser une variété d'algorithmes fournis par la bibliothèque standard et des boucles for basées sur une plage. Une approche élégante à cela est expliquée ici https://foonathan.net/2017/03/tuple-iterator/ . L'idée de base est de transformer les tuples en une plage avec begin()et des end()méthodes pour fournir des itérateurs. L'itérateur lui-même renvoie un std::variant<...>qui peut ensuite être visité à l'aide de std::visit.

Voici quelques exemples:

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

Ma mise en œuvre (qui est fortement basée sur les explications dans le lien ci-dessus):

#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'accès en lecture seule est également pris en charge en passant un const std::tuple<>&à to_range().


0

En développant la réponse @Stypox, nous pouvons rendre leur solution plus générique (C ++ 17 et plus). En ajoutant un argument de fonction appelable:

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

Ensuite, nous avons besoin d'une stratégie pour visiter chaque type.

Commençons par quelques helpers (les deux premiers tirés de 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 est utilisé pour permettre la modification de l'état des tuples.

Usage:

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

Résultat:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Pour être complet, voici mon 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;}
};
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.