138 votes

Comment itérer sur les éléments d'un std::tuple ?

Comment itérer sur 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 : sorry, unimplemented : cannot expand 'Listener ...' into a fixed-length argument list.
Erreur 2 : i ne peut pas apparaître dans une expression constante.

Alors, comment faire pour itérer correctement sur les éléments d'un tuple ?

1voto

bit2shift Points 491

J'ai peut-être raté ce train, mais ce sera là pour une référence future.
Voici ce que j'ai construit 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);

    /* ... */
}

Des améliorations sont possibles.


Selon le code de l'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);

1voto

joki Points 782

De toutes les réponses que j'ai vues ici, aquí y aquí J'ai aimé @sigidagi La meilleure façon d'itérer de la part de l'entreprise. Malheureusement, sa réponse est très verbeuse, ce qui, à mon avis, masque la clarté inhérente.

Voici ma version de sa solution qui est plus concise et fonctionne avec std::tuple , std::pair y 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 mis en œuvre pour C++11 .

1voto

Thomas Legris Points 79

En développant la réponse de @Stypox, nous pouvons rendre leur solution plus générique (à partir de C++17). 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 aides (les deux premières proviennent 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.

Utilisation :

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

0voto

tmaric Points 1735

Je suis tombé sur le même problème pour l'itération sur un tuple d'objets de fonction, donc voici 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; 
}

Sortie :

A 
B 
C 
D

-1voto

Slava Points 4119

Le tuple de boost fournit des fonctions d'aide get_head() y get_tail() Ainsi, vos fonctions d'aide 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()); }

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

avec std::tuple cela devrait être similaire.

En fait, malheureusement std::tuple ne semble pas fournir une telle interface, donc les méthodes suggérées précédemment devraient fonctionner, ou vous devriez passer à l'option boost::tuple qui présente d'autres avantages (comme les opérateurs io déjà fournis). Bien qu'il y ait un inconvénient à 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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X