2014-12-02 10 views
5

Vor einiger Zeit wurde eine Lösung zum Ausdrucken von std :: tuple gepostet: here. Zum größten Teil verstehe ich, was passiert. Ich habe jedoch Probleme zu verstehen, was in der print_tuple-Funktion passiert.Anatomie des hübschen Drucktupels

template<class Ch, class Tr, class Tuple, std::size_t... Is> 
void print_tuple(std::basic_ostream<Ch,Tr>& os, Tuple const& t, seq<Is...>){ 
    using swallow = int[]; 
    (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...}; 
} 

Ich verstehe nicht, was im Körper dieser Funktion passiert. Soweit ich das beurteilen kann, hat es mit dem Auspacken zu tun Is. Ich bekomme, dass die Bedingung, Is == 0 überprüft, ob wir an der Spitze Element sind.

Also, was ist los?

+5

Der Code konstruiert (und verwirft dann) ein 'int []' Array aus einer Initialisierungsliste, in der jedes Element 0 ist, aber ein Element des Tupels als Nebeneffekt (über Kommaoperator) ausgegeben wird. Die Verwendung der Initialisierungsliste dient nur dazu, in einen Kontext zu gelangen, in dem die Erweiterung des Pakets funktionieren würde. –

+0

Ah! Also ist das 'swallow {...}' Konstrukt eine Initialisierungsliste für int []. Ich habe das auf den ersten Blick nicht verstanden. – sguzman

Antwort

9

der durch ein Beispiel mit einem beliebigen Tupel gehen lassen, sagen:

using Tuple = tuple<char, int, string>; 

die Integer-Sequenz also, die unsere Funktion mit aufgerufen werden:

in dem
seq<0, 1, 2> 

Und unsere Expansion Pack body ist:

(void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...}; 

was, wenn wir es manuell erweitern, wie der Compiler würde, wird:

(void)swallow{0, 
       (void(os << (0 == 0? "" : ", ") << std::get<0>(t)), 0), 
       (void(os << (1 == 0? "" : ", ") << std::get<1>(t)), 0), 
       (void(os << (2 == 0? "" : ", ") << std::get<2>(t)), 0) 
       }; 

Und dann die Auswertung der Zweige:

(void)swallow{0, 
       (void(os << "" << std::get<0>(t)), 0), 
       (void(os << ", " << std::get<1>(t)), 0), 
       (void(os << ", " << std::get<2>(t)), 0) 
       }; 

das heißt, wir sind eine ganze Reihe von 4 0 s, mit dem Nebeneffekt von Ausdrucken der Inhalte des Tupels konstruieren, Komma-getrennt, um sicherzustellen, dass wir nicht mit einem zusätzlichen Komma beginnen. Die vier Ausdrücke müssen in der Reihenfolge ausgewertet werden, damit garantiert wird, dass die Tupelinhalte in Reihenfolge gedruckt werden.

Die ursprüngliche (void) Umwandlung ist nur dazu da, die Warnung über nicht verwendete Variablen zu vermeiden, die Compiler ausgeben sollten, wenn alle Warnungen aktiviert sind. Die initiale 0 in der Array-Initialisierung behandelt den Fall, in dem das Tupel leer ist.

+0

Ich fange an es zu bekommen. Was macht die "Leere" (...)? – sguzman

+1

@SalvadorGuzman Wirf das Ergebnis von 'os << was auch immer' void', so dass der Ausdruck '(void (stuff), 0)' definitiv '0' zurückgibt. Dies geschieht, wenn sich jemand dafür entscheidet, eine Überladung für 'operator,()' zu schreiben, die etwas anderes tut, als das 'int' zurückzugeben. – Barry

+0

Das macht viel mehr Sinn. Eine letzte Frage. Ist also der '...' Operator ein Entpacker? Kann ich darauf zählen, dass Komma-getrennte Werte zurückgegeben werden? – sguzman

1

(os << (Is == 0? "" : ", ") << std::get<Is>(t)) Drucke das Is ten Element (mit dem Präfix für ", "Is > 0)

Dann, um das Ergebnis void Gießen möglich Überlastung des Komma-Operators zu vermeiden.

(/*previous stuff*/, 0)... hat eine Serie von 0

{0, /*previous stuff*/ } die Fälle, in denen verwaltet sizeof...(Is) == 0

swallow /*previous stuff*/ build ein Array von int 0.

void /*previous stuff*/: In Leere umwandeln, um eine Warnung zu vermeiden.