web-dev-qa-db-de.com

Wie können Sie die Elemente eines std :: tuple durchlaufen?

Wie kann ich über ein Tuple iterieren (mit C++ 11)? Ich habe folgendes versucht:

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

aber das funktioniert nicht:

Fehler 1: Entschuldigung, nicht implementiert: "Listener ..." kann nicht in eine Liste mit fester Länge erweitert werden.
Fehler 2: Ich kann nicht in einem konstanten Ausdruck erscheinen.

Wie kann ich also die Elemente eines Tupels richtig durchlaufen?

74
1521237

Boost.Fusion ist eine Möglichkeit:

Ungetestetes Beispiel:

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

Tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
21
Éric Malenfant

Ich habe eine Antwort basierend auf über einen Tupel iterieren :

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

Die übliche Idee ist die Rekursion der Kompilierzeit. In der Tat wird diese Idee verwendet, um eine printf zu machen, die typsicher ist, wie in den Original-Tuple-Papieren vermerkt.

Dies kann leicht zu einem for_each für Tupel verallgemeinert werden:

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

Dies erfordert jedoch einige Anstrengungen, um FuncT etwas mit den entsprechenden Überladungen für jeden Typ, den das Tuple enthalten kann, darstellen zu lassen. Dies funktioniert am besten, wenn Sie wissen, dass alle Tuple-Elemente eine gemeinsame Basisklasse oder etwas Ähnliches haben.

113
emsr

Verwenden Sie Boost.Hana und generische Lambdas:

#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

In C++ 17 können Sie Folgendes tun:

std::apply([](auto ...x){std::make_Tuple(x.do_something()...);} , the_Tuple);

Dies funktioniert bereits in Clang ++ 3.9 mit std :: experimental :: apply.

15
M. Alaggan

Sie müssen die Template-Metaprogrammierung verwenden, die hier mit Boost.Tuple dargestellt ist:

#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 können Sie stattdessen print_Tuple() als variadische Vorlagenfunktion schreiben.

9

C++ führt zu diesem Zweck Erweiterungsanweisungen ein. Sie befanden sich ursprünglich auf dem Weg zu C++ 20, verfehlten jedoch den Schnitt aufgrund fehlender Zeit für die Überprüfung der Sprachformulierungen (siehe hier und hier ).

Die derzeit vereinbarte Syntax (siehe die obigen Links) lautet:

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

In C++ 17 können Sie std::apply mit fold Ausdruck verwenden:

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

Ein vollständiges Beispiel zum Drucken eines Tupels:

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

[Online-Beispiel auf Coliru]

Diese Lösung löst die Frage der Evaluierungsreihenfolge in M. Alaggans Antwort .

8
xskxzr

Definieren Sie zunächst einige Indexhelfer:

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

Mit Ihrer Funktion möchten Sie sich für jedes Tuple-Element bewerben:

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

du kannst schreiben:

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

Wenn foovoid zurückgibt, verwenden Sie

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

Hinweis: In C++ 14 ist make_index_sequence bereits definiert ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Wenn Sie eine Evaluierungsreihenfolge von links nach rechts benötigen, sollten Sie Folgendes berücksichtigen:

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

Wenn Sie std :: Tuple verwenden möchten und einen C++ - Compiler verwenden, der variadic-Templates unterstützt, versuchen Sie den folgenden Code (getestet mit g ++ 4.5). Dies sollte die Antwort auf Ihre Frage sein.

#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 ist eine weitere Option, erfordert jedoch einen eigenen Tuple-Typ: boost :: fusion :: Tuple. Bleibt besser beim Standard! Hier ist ein 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;
}

die kraft variabischer vorlagen!

5
sigidagi

Hier ist eine einfache C++ 17-Methode zum Durchlaufen von Tuple-Elementen mit einer Standardbibliothek:

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

Beispiel:

#include <iostream>

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

Ausgabe:

1
a

Dies kann erweitert werden, um die Schleife bedingt zu unterbrechen, falls das Callable einen Wert zurückgibt (aber mit Callables funktioniert, die keinen bool zuweisbaren Wert zurückgeben, z.

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

Beispiel:

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

Ausgabe:

1
a
---
1
3

das Tuple des Boobs bietet Hilfsfunktionen get_head() und get_tail(), so dass Ihre Hilfsfunktionen so aussehen können:

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

wie hier beschrieben http://www.boost.org/doc/libs/1_34_0/libs/Tuple/doc/Tuple_advanced_interface.html

mit std::Tuple sollte es ähnlich sein.

Leider scheint std::Tuple keine solche Schnittstelle zu bieten, so dass die zuvor vorgeschlagenen Methoden funktionieren sollten, oder Sie müssten zu boost::Tuple wechseln, was andere Vorteile hat (wie z. B. bereits vorhandene io-Operatoren). Es gibt zwar einen Nachteil von boost::Tuple mit gcc - es akzeptiert noch keine variadischen Vorlagen, aber das kann schon behoben sein, da auf meinem Rechner keine neueste Boost-Version installiert ist.

1
Slava

Eine einfachere, intuitivere und compilerfreundlichere Möglichkeit, dies in C++ 17 mit if constexpr zu tun:

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

Dies ist eine Rekursion während der Kompilierung, ähnlich der von @emsr. Aber dies verwendet SFINAE nicht, also (glaube ich), ist es compilerfreundlicher.

1
Stypox

Ich könnte diesen Zug vermisst haben, aber dies wird hier als Referenz dienen.
Hier ist mein Konstrukt basierend auf dieser Antwort und diesem 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){}
};

Sie verwenden es dann wie folgt:

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

    /* ... */
}

Es könnte Raum für Verbesserungen geben.


Gemäß dem Code des OPs würde dies folgendermaßen aussehen:

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

In MSVC STL gibt es eine _For_each_Tuple_element-Funktion (nicht dokumentiert):

#include <Tuple>

// ...

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

Die Verwendung von constexprNAME _ und if constexpr (C++ 17) ist recht einfach und unkompliziert:

_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

Von allen Antworten, die ich hier gesehen habe, - hier und hier , hat mir gefallen - @sigidagi 's am besten durchlaufen. Leider ist seine Antwort sehr ausführlich, was meiner Meinung nach die inhärente Klarheit verdeckt.

Dies ist meine Version seiner Lösung, die übersichtlicher ist und mit std::Tuple, std::pair und std::array arbeitet.

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

std::make_index_sequence von C++ 14 kann für C++ 11 implementiert werden.

0
joki

Andere haben einige gut konzipierte Bibliotheken von Drittanbietern erwähnt, an die Sie sich wenden können. Wenn Sie jedoch C++ ohne diese Fremdanbieter-Bibliotheken verwenden, kann der folgende Code hilfreich sein.

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

Anmerkung: Der Code wird mit jedem Compiler kompiliert, der C++ 11 unterstützt, und er bleibt mit dem Design der Standardbibliothek konsistent:

  1. Das Tuple muss nicht std::Tuple sein, sondern kann alles sein, das std::get und std::Tuple_size unterstützt. insbesondere können std::array und std::pair verwendet werden;

  2. Das Tupel kann ein Referenztyp sein oder cv-qualifiziert sein;

  3. Es verhält sich ähnlich wie std::for_each und gibt die Eingabe UnaryFunction zurück.

  4. Für C++ 14 (oder spätere Version) Benutzer könnten typename std::enable_if<T>::type und typename std::decay<T>::type durch ihre vereinfachte Version std::enable_if_t<T> und std::decay_t<T> ersetzt werden.

  5. Für C++ 17-Benutzer (oder die spätere Version) kann std::Tuple_size<T>::value durch die vereinfachte Version std::Tuple_size_v<T> ersetzt werden.

  6. Für C++ 20-Benutzer (oder die spätere Version) kann die SFINAE-Funktion mit der Concepts implementiert werden.

0
M. W.

Ich bin über dasselbe Problem gestolpert, weil ich über ein Tuple von Funktionsobjekten iteriert habe. Hier ist eine weitere Lösung:

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

Ausgabe: 

A 
B 
C 
D
0
tmaric