2014-11-13 6 views
17

ich folgenden Tupel gemacht haben:Wie 11 ++ ein std :: tuple in C iterieren

ich wissen will, wie soll ich über sie iterieren? Es gibt tupl_size(), aber beim Lesen der Dokumentation habe ich nicht verstanden, wie man es benutzt. Auch ich habe SO gesucht, aber Fragen scheinen um Boost::tuple zu sein.

auto some = make_tuple("I am good", 255, 2.1); 
+2

Hängt davon ab, was Sie vorhaben, nachdem Sie darüber "iteriert" haben. – Rapptz

+1

Was würde auf Daten verschiedener Typen iterieren für Sie bedeuten? –

+0

Ich möchte jedes Element "cout" - die einfachste Iteration. Zugriff auf jedes Element für 'cout'-ing ... –

Antwort

20

Hier ist ein Versuch, die Iteration über ein Tupel in Komponententeile zu zerlegen.

Erstens, eine Funktion, die eine Reihenfolge von Operationen in der Reihenfolge darstellt. Beachten Sie, dass viele Compiler dies schwer zu verstehen, obwohl es legal ist C++ 11 so weit wie ich kann sagen:

template<class... Fs> 
void do_in_order(Fs&&... fs) { 
    int unused[] = { 0, ((void)std::forward<Fs>(fs)(), 0)... } 
    (void)unused; // blocks warnings 
} 

nächstes eine Funktion, die eine std::tuple nimmt und extrahiert die Indizes benötigt, um jedes Element zugreifen . Auf diese Weise können wir uns später weiterentwickeln.

Als Nebeneffekt, unterstützt mein Code std::pair und std::array Iteration:

template<class T> 
constexpr std::make_index_sequence<std::tuple_size<T>::value> 
get_indexes(T const&) 
{ return {}; } 

Das Fleisch und Kartoffeln:

template<size_t... Is, class Tuple, class F> 
void for_each(std::index_sequence<Is...>, Tuple&& tup, F&& f) { 
    using std::get; 
    do_in_order([&]{ f(get<Is>(std::forward<Tuple>(tup))); }...); 
} 

und die Öffentlichkeit gerichtete Schnittstelle:

template<class Tuple, class F> 
void for_each(Tuple&& tup, F&& f) { 
    auto indexes = get_indexes(tup); 
    for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); 
} 

während es Tuple angibt, funktioniert es auf std::array s und std::pair s. Es leitet auch die R/L-Wertkategorie des Objekts an das Funktionsobjekt weiter, das es aufruft. Beachten Sie außerdem Folgendes: Wenn Sie eine freie Funktion get<N> auf Ihrem benutzerdefinierten Typ haben und Sie get_indexes überschreiben, funktioniert das obige for_each auf Ihrem benutzerdefinierten Typ.

Wie bereits erwähnt, do_in_order während ordentlich ist nicht von vielen Compilern unterstützt, wie sie nicht die Lambda mit nicht erweiterten Parameter Packs in Parameter Packs erweitert werden.

Wir können do_in_order in diesem Fall Inline

template<size_t... Is, class Tuple, class F> 
void for_each(std::index_sequence<Is...>, Tuple&& tup, F&& f) { 
    using std::get; 
    int unused[] = { 0, ((void)f(get<Is>(std::forward<Tuple>(tup)), 0)... } 
    (void)unused; // blocks warnings 
} 

dies nicht viel Ausführlichkeit kosten, aber ich persönlich finde es weniger klar. Die Schattenmagie, wie do_in_order funktioniert, ist meiner Meinung nach inline verdeckt.

index_sequence (und unterstützende Vorlagen) ist eine C++ 14-Funktion, die in C++ 11 geschrieben werden kann. Eine solche Implementierung beim Stack-Überlauf zu finden, ist einfach.Ein aktueller Top-Google-Hit ist a decent O(lg(n)) depth implementation, die, wenn ich die Kommentare richtig gelesen habe, die Grundlage für mindestens eine Iteration der tatsächlichen gcc make_integer_sequence sein kann (die Kommentare weisen auch auf einige weitere Kompilierzeit Verbesserungen rund um die Beseitigung von sizeof... Anrufe).

Alternativ können wir schreiben:

template<class F, class...Args> 
void for_each_arg(F&&f,Args&&...args){ 
    using discard=int[]; 
    (void)discard{0,((void)(
    f(std::forward<Args>(args)) 
),0)...}; 
} 

Und dann:

template<size_t... Is, class Tuple, class F> 
void for_each(std::index_sequence<Is...>, Tuple&& tup, F&& f) { 
    using std::get; 
    for_each_arg(
    std::forward<F>(f), 
    get<Is>(std::forward<Tuple>(tup))... 
); 
} 

Welche das Handbuch vermeidet erweitern noch weitere Compiler kompiliert auf. Wir übergeben die Is über den Parameter auto&&i.

In C++ 1z können wir auch mit einem for_each_arg Funktionsobjekt verwenden, um den Index fiddling zu beseitigen.

+0

ADL für "get" ist eine nette Geste. 'tuple_size' arbeitet auch mit Arrays und Paaren, so dass ich keine separaten 'get_indexes'-Überladungen benötige - auch, -> declltype (std :: make_index_sequence ())' - why benutze 'decltype'? –

+0

@ T.C. Nun, ich habe mit 'auto' begonnen und dann SFINAE gemacht, indem ich den 'return' Ausdruck zu' -> 'und' return {} 'gebracht habe. Ich dachte nicht daran, den 'declltype' loszuwerden. Es wurde in eine direkte 'make_index_sequence' von' tuple_size' geändert und Überladungen wurden beendet. Und einen Tippfehler korrigiert (vermisste ein '...'). – Yakk

+0

@ T.C., Yakk: Kann man erklären, warum "ADL for' get' "eine" nette Geste "ist? Was ist der Vorteil gegenüber der direkten Verwendung von 'std :: get' im Funktionsaufruf? – davidhigh

27
template<class F, class...Ts, std::size_t...Is> 
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func, std::index_sequence<Is...>){ 
    using expander = int[]; 
    (void)expander { 0, ((void)func(std::get<Is>(tuple)), 0)... }; 
} 

template<class F, class...Ts> 
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func){ 
    for_each_in_tuple(tuple, func, std::make_index_sequence<sizeof...(Ts)>()); 
} 

Verbrauch:

auto some = std::make_tuple("I am good", 255, 2.1); 
for_each_in_tuple(some, [](const auto &x) { std::cout << x << std::endl; }); 

Demo.

std::index_sequence und Familie sind C++ 14 Funktionen, aber sie können leicht in C++ 11 implementiert werden (es gibt viele verfügbar auf SO). Polymorphe Lambdas sind ebenfalls C++ 14, können aber durch einen benutzerdefinierten Funktor ersetzt werden.

+2

+1 aber Sie sollten erwähnen, dass dies nur C++ 14 ist .. (der Titel ist C++ 11) – quantdev

+0

Was Konzept ist dies: [Klasse gefolgt von "..." und dann Tx], ich weiß nicht wie sollte ich es aussprechen, ist das einzige, was ich verstehen kann, dass es ein variables Argument für Tuple-Werte gibt, da es logisch korrekt ist, dass Tupel gona verschiedene Typen hat, aber obwohl ich es lieben würde, wenn jemand mich an den richtigen Ort leiten könnte woher kann ich das lernen? –

+4

Dies sind Variadic Vorlagen: http://msdn.microsoft.com/en-us/library/dn439779.aspx und http://en.cppreference.com/w/cpp/language/parameter_pack –

10

Hier ist eine ähnliche und ausführlichere Lösung als die früher einer von TC gegeben angenommen, die vielleicht ein wenig leichter zu verstehen (- es ist wahrscheinlich das gleiche wie tausend andere da draußen im Netz):

template<typename TupleType, typename FunctionType> 
void for_each(TupleType&&, FunctionType 
      , std::integral_constant<size_t, std::tuple_size<typename std::remove_reference<TupleType>::type >::value>) {} 

template<std::size_t I, typename TupleType, typename FunctionType 
     , typename = typename std::enable_if<I!=std::tuple_size<typename std::remove_reference<TupleType>::type>::value>::type > 
void for_each(TupleType&& t, FunctionType f, std::integral_constant<size_t, I>) 
{ 
    f(std::get<I>(t)); 
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, I + 1>()); 
} 

template<typename TupleType, typename FunctionType> 
void for_each(TupleType&& t, FunctionType f) 
{ 
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, 0>()); 
} 

Usage (mit std::tuple):

auto some = std::make_tuple("I am good", 255, 2.1); 
for_each(some, [](const auto &x) { std::cout << x << std::endl; }); 

Usage (mit std::array):

std::array<std::string,2> some2 = {"Also good", "Hello world"}; 
for_each(some2, [](const auto &x) { std::cout << x << std::endl; }); 

DEMO


Allgemeine Idee: wie in der Lösung von T. C., beginnen Sie mit einem Index I=0 und die Größe des Tupels nach oben. Allerdings wird hier nicht per variadic Expansion, sondern eins nach dem anderen getan.

Erläuterung:

  • Die erste Überlast von for_each wird aufgerufen, wenn I auf die Größe des Tupels entspricht. Die Funktion tut dann einfach nichts und so endet die Rekursion.

  • Die zweite Überlast ruft die Funktion mit dem Argument std::get<I>(t) auf und erhöht den Index um eins. Die Klasse std::integral_constant wird benötigt, um den Wert I zur Kompilierzeit aufzulösen. Der SFASE-Code std::enable_if hilft dem Compiler, diese Überladung von der vorherigen zu trennen, und ruft diese Überladung nur dann auf, wenn die I kleiner als die Tupelgröße ist (auf Coliru wird dies benötigt, in Visual Studio dagegen ohne).

  • Der dritte startet die Rekursion mit I=0. Es ist die Überlastung, die normalerweise von außen genannt wird.




EDIT: Ich habe auch die Idee von Yakk erwähnt eingeschlossen zu unterstützen zusätzlich std::array und std::pair durch einen allgemeinen Template-Parameter TupleType statt eines verwenden, das ist spezialisiert auf std::tuple<Ts ...>.

Als TupleType Typ muss abgeleitet werden und ist so eine "universelle Referenz", dies hat weiterhin den Vorteil, dass man kostenlos die perfekte Weiterleitung bekommt. Der Nachteil ist, dass man eine andere Indirection über typename std::remove_reference<TupleType>::type verwenden muss, da TupleType auch ein Referenztyp sein könnte.

+0

Dies ist keine C++ 11-Lösung, es ist eine C++ 1y-Lösung. –

+0

@ EdwardZ.Yang: Der Basiscode ist C++ 11, nur das Beispiel mit dem generischen Lambda erfordert C++ 14. Aber danke für die Nachricht. – davidhigh

Verwandte Themen