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


112

Wie kann ich über ein Tupel 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 Argumentliste mit fester Länge erweitert werden.
Fehler 2: Ich kann nicht in einem konstanten Ausdruck erscheinen.

Wie iteriere ich also korrekt über die Elemente eines Tupels?


2
Darf ich fragen, wie Sie in C ++ 0x kompilieren? Soweit ich weiß, ist es weder veröffentlicht noch fertig.
Burkhard

5
g ++ enthält experimentelle Unterstützung einiger C ++ 0X-Funktionen, einschließlich variabler Vorlagen, seit Version 4.3. Andere Compiler tun dasselbe (mit unterschiedlichen Funktionssätzen, wenn Sie sie in der Produktion verwenden möchten, sind Sie wieder in den 90ern mit einer großen Variation der Unterstützung für
hochmoderne

Ich benutze g ++ Version 4.4 mit std = c ++ 0x

9
Diese Frage benötigt ein C ++ 11-Update.
Omnifarious

2
@ Omnifarious jetzt, es braucht ein C ++ 14 Update
pepper_chico

Antworten:


26

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

@ViktorSehr AFAICT nicht (zumindest auf GCC 4.7.2)? Jemand mit einem Hinweis?
sehe

@ViktorSehr Das Problem wurde gefunden: Ein Fehler / eine Auslassung bewirkt, dass das Verhalten der Fusion von der Reihenfolge der Includes abhängt. Weitere Informationen finden Sie unter Boost-Ticket Nr. 8418
siehe

Sie müssen boost :: fusion :: tuple anstelle von std :: tuple verwenden, damit dies funktioniert.
Marcin

Unter GCC 8.1 / mingw-64 erhalte ich zwei Warnungen für die Verwendung von boost :: fusion :: for_each mit Standard-Lambda-Ausdrücken: boost / mpl / assert.hpp: 188: 21: Warnung: Unnötige Klammern in der Deklaration von 'assert_arg' [-Wparentheses] fehlgeschlagen ************ (Pred :: ************ boost / mpl / assert.hpp: 193: 21: Warnung: Unnötige Klammern in Deklaration von 'assert_not_arg' [-Wparentheses] fehlgeschlagen ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein

129

Ich habe eine Antwort, die auf dem Iterieren über ein Tupel basiert :

#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 Kompilierungszeit. Tatsächlich wird diese Idee verwendet, um einen Ausdruck zu erstellen, der typsicher ist, wie in den Original-Tupelpapieren angegeben.

Dies kann leicht in ein for_eachfü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 dann jedoch einige Anstrengungen, um FuncTfür jeden Typ, den das Tupel enthalten könnte, etwas mit den entsprechenden Überladungen darzustellen. Dies funktioniert am besten, wenn Sie wissen, dass alle Tupelelemente eine gemeinsame Basisklasse oder ähnliches haben.


5
Danke für das schöne einfache Beispiel. Informationen zu C ++ - Anfängern, die Hintergrundinformationen zur Funktionsweise suchen, finden Sie in SFINAE und in der enable_ifDokumentation .
Faheem Mitha

Dies könnte leicht als generisch verallgemeinert werden for_each. Tatsächlich habe ich es selbst gemacht. :-) Ich denke, diese Antwort wäre nützlicher, wenn sie bereits verallgemeinert wäre.
Omnifarious

4
Dort habe ich die Verallgemeinerung hinzugefügt, weil ich tatsächlich eine brauchte, und ich denke, es wäre nützlich, wenn andere sie sehen würden.
Omnifarious

2
Hinweis: Möglicherweise benötigen Sie auch Versionen mit const std::tuple<Tp...>&.. Wenn Sie nicht beabsichtigen, Tupel während der Iteration zu ändern, constreichen diese Versionen aus.
tödliche Gitarre

2
Nicht wie geschrieben. Sie könnten eine Version mit umgedrehter Indizierung erstellen - beginnen Sie bei I = sizeof ... (Tp) und zählen Sie herunter. Geben Sie dann explizit eine maximale Anzahl von Argumenten an. Sie können auch eine Version erstellen, die bei einem Tag-Typ fehlerhaft ist, z. B. break_t. Dann würden Sie ein Objekt dieses Tag-Typs in Ihr Tupel einfügen, wenn Sie den Druckvorgang beenden möchten. Oder Sie können einen Stopptyp als Vorlagenparameter angeben. Offensichtlich konnte man zur Laufzeit nicht brechen.
Emsr

54

In C ++ 17 können Sie std::applymit dem Fold-Ausdruck Folgendes 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 das Problem der Bewertungsreihenfolge in der Antwort von M. Alaggan .


1
Können Sie erklären, was hier passiert ((std::cout << args << '\n'), ...);? Das Lambda wird einmal mit den entpackten Tupelelementen aufgerufen args, aber was ist mit den doppelten Klammern los?
Helmesjo

4
@helmesjo Hier wird es zu einem Komma-Ausdruck erweitert ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n')).
xskxzr

Beachten Sie, dass Sie, wenn Sie Dinge tun möchten, die in einem Komma-Ausdruck nicht legal sind (z. B. Variablen und Blöcke deklarieren), all dies in eine Methode einfügen und sie einfach aus dem gefalteten Komma-Ausdruck heraus aufrufen können.
Miral

24

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

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

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


4
Führt dies nicht dazu, dass die Iteration - dh Aufrufe von do_something()- in einer nicht angegebenen Reihenfolge erfolgt, weil das Parameterpaket innerhalb eines Funktionsaufrufs erweitert wird (), bei dem Argumente eine nicht angegebene Reihenfolge haben? Das könnte sehr bedeutsam sein; Ich würde mir vorstellen, dass die meisten Leute erwarten würden, dass die Bestellung garantiert in derselben Reihenfolge wie die Mitglieder erfolgt, dh wie die Indizes std::get<>(). AFAIK, um in solchen Fällen eine garantierte Bestellung zu erhalten, muss die Erweiterung innerhalb erfolgen {braces}. Liege ich falsch? Diese Antwort betont eine solche Reihenfolge: stackoverflow.com/a/16387374/2757035
underscore_d

21

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


4
Bitte, bitte, bitte geh nicht using namespace boost::fusion(besonders zusammen mit using namespace std). Jetzt gibt es keine Möglichkeit zu wissen, ob das for_eachist std::for_eachoderboost::fusion::for_each
Bulletmagnet

3
@Bulletmagnet Dies wurde aus Gründen der Knappheit hier durchgeführt und ADL kann dies problemlos verarbeiten. Außerdem ist es auch lokale Funktion.
Pepper_chico

16

Zu diesem Zweck führt C ++ Erweiterungsanweisungen ein . Sie waren ursprünglich auf dem richtigen Weg für C ++ 20, haben den Schnitt jedoch nur knapp verpasst, da nicht genügend Zeit für die Überprüfung der Sprachformulierung vorhanden war (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;
}

15

Eine einfachere, intuitivere und compilerfreundlichere Methode in C ++ 17 mit 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);
}

Dies ist eine Rekursion zur Kompilierungszeit, ähnlich der von @emsr. Da dies jedoch kein SFINAE verwendet, ist es (glaube ich) compilerfreundlicher.


8

Sie müssen die Metaprogrammierung von Vorlagen verwenden, die hier mit Boost.Tuple angezeigt wird:

#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 print_tuple()stattdessen als variable Vorlagenfunktion schreiben .


8

Definieren Sie zunächst einige Index-Helfer:

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 auf jedes Tupelelement anwenden:

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

Oder wenn foozurückkommt void, verwenden Sie

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

Hinweis: Unter C ++ 14 make_index_sequenceist bereits definiert ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Wenn Sie eine Bewertungsreihenfolge 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)>());
}

1
Sollte vor dem Aufrufen den Rückgabewert von footo setzen , um eine mögliche Überlastung des pathologischen Operators zu vermeiden. voidoperator,
Yakk - Adam Nevraumont

7

Hier ist eine einfache C ++ 17-Methode zum Durchlaufen von Tupelelementen mit nur 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 der Callable einen Wert zurückgibt (aber immer noch mit Callables funktioniert, die keinen zuweisbaren Bool-Wert zurückgeben, z. B. 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)...);
    }
}

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

5

Wenn Sie std :: tuple verwenden möchten und einen C ++ - Compiler haben, der verschiedene Vorlagen unterstützt, versuchen Sie es mit dem 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 Tupeltyp: boost :: fusion :: tuple. Halten wir uns besser an den 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 variabler Vorlagen!


Ich habe Ihre erste Lösung ausprobiert, aber sie schlägt mit dieser Funktion paarweise fehl. Irgendeine Idee warum? Template <Typname T, Typname U> void addt (Paar <T, U> p) {cout << p.first + p.second << endl; } int main (int argc, char * argv []) {cout << "Hallo." << 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

Es ist eine Schande, dass diese Antwort so ausführlich geschrieben ist, weil ich denke, dass die Art der Iteration (for_each_impl) die eleganteste aller Lösungen ist, die ich gesehen habe.
Joki

3

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

2

Andere haben einige gut gestaltete Bibliotheken von Drittanbietern erwähnt, an die Sie sich wenden können. Wenn Sie jedoch C ++ ohne diese Bibliotheken von Drittanbietern 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);
}

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

  1. Das Tupel muss nicht sein std::tupleund kann stattdessen alles sein, was std::getund unterstützt std::tuple_size; insbesondere std::arrayund std::pairkann verwendet werden;

  2. Das Tupel kann ein Referenztyp oder ein Lebenslauf sein.

  3. Es hat ein ähnliches Verhalten wie std::for_eachund gibt die Eingabe zurück UnaryFunction.

  4. Für C ++ 14 (oder Laster-Version) Benutzer typename std::enable_if<T>::typeund typename std::decay<T>::typekönnte durch ihre vereinfachte Version ersetzt werden, std::enable_if_t<T>und std::decay_t<T>;

  5. Für C ++ 17 (oder Laster-Version) Benutzer std::tuple_size<T>::valuekönnte durch die vereinfachte Version ersetzt werden std::tuple_size_v<T>.

  6. Für Benutzer von C ++ 20 (oder einer Laster-Version) SFINAEkönnte die Funktion mit dem implementiert werden Concepts.


2

Mit constexprund if constexpr(C ++ 17) ist dies ziemlich 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

Ich hätte diesen Zug vielleicht verpasst, aber dies wird hier sein, um später darauf zurückgreifen zu können.
Hier ist mein Konstrukt basierend auf dieser Antwort und diesem Kern :

#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 OP-Code 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

Von all den Antworten, die ich hier, hier und hier gesehen habe , mochte ich @sigidagis Art, am besten zu iterieren. Leider ist seine Antwort sehr ausführlich, was meiner Meinung nach die inhärente Klarheit verdeckt.

Dies ist meine Version seiner Lösung , die prägnanten und arbeitet mit std::tuple, std::pairund 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>()
    );
}

Demo: coliru

C ++ 14 std::make_index_sequencekann für C ++ 11 implementiert werden .


0

Auftrieb des Tupels stellt Hilfsfunktionen get_head()und get_tail()so Ihre Hilfsfunktionen wie folgt aussehen:

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::tupleihm sollte ähnlich sein.

Tatsächlich std::tuplescheint eine solche Schnittstelle leider nicht verfügbar zu sein, daher sollten die zuvor vorgeschlagenen Methoden funktionieren, oder Sie müssten zu einer wechseln, boost::tupledie andere Vorteile bietet (wie z. B. bereits bereitgestellte Io-Operatoren). Obwohl es einen Nachteil von boost::tuplegcc gibt - es akzeptiert noch keine variablen Vorlagen, aber das kann bereits behoben sein, da ich nicht die neueste Version von Boost auf meinem Computer installiert habe.


0

Ich bin auf dasselbe Problem gestoßen, als ich über ein Tupel 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

Eine andere Möglichkeit wäre, Iteratoren für Tupel zu implementieren. Dies hat den Vorteil, dass Sie eine Vielzahl von Algorithmen verwenden können, die von der Standardbibliothek bereitgestellt werden und bereichsbasiert für Schleifen sind. Ein eleganter Ansatz hierzu wird hier erklärt: https://foonathan.net/2017/03/tuple-iterator/ . Die Grundidee besteht darin, Tupel in einen Bereich mit begin()und end()Methoden zur Bereitstellung von Iteratoren umzuwandeln. Der Iterator selbst gibt ein zurück, std::variant<...>das dann mit besucht werden kann std::visit.

Hier einige Beispiele:

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

Meine Implementierung (die stark auf den Erklärungen im obigen Link basiert):

#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

Der schreibgeschützte Zugriff wird auch durch Übergabe von a const std::tuple<>&an unterstützt to_range().


0

Wenn wir die @ Stypox-Antwort erweitern, können wir ihre Lösung allgemeiner gestalten (ab C ++ 17). Durch Hinzufügen eines aufrufbaren Funktionsarguments:

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

Dann brauchen wir eine Strategie, um jeden Typ zu besuchen.

Beginnen wir mit einigen Helfern (die ersten beiden stammen aus 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 wird verwendet, um den Status von Tupeln zu ändern.

Verwendung:

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

Ergebnis:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Der Vollständigkeit halber hier meine 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;}
};
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.