Stampa std :: tupla

Questo è un seguito alla mia precedente domanda sui contenitori STL pretty-printing , per i quali siamo riusciti a sviluppare una soluzione molto elegante e completamente generale.


In questo prossimo passo, vorrei includere pretty-printing per std::tuple , usando modelli variadic (quindi questo è rigorosamente C ++ 11). Per std::pair , dico semplicemente

 std::ostream & operator<<(std::ostream & o, const std::pair & p) { return o << "(" << p.first << ", " << p.second << ")"; } 

Qual è l’analoga costruzione per stampare una tupla?

Ho provato vari tipi di stack di argomenti template decomprimendo, passando gli indici e usando SFINAE per scoprire quando sono all’ultimo elemento, ma senza successo. Non ti caricherò del mio codice spezzato; la descrizione del problema è probabilmente abbastanza semplice. In sostanza, mi piacerebbe il seguente comportamento:

 auto a = std::make_tuple(5, "Hello", -0.1); std::cout << a << std::endl; // prints: (5, "Hello", -0.1) 

Punti bonus per includere lo stesso livello di generalità (char / wchar_t, delimitatori di coppia) come la domanda precedente!

Yay, indici ~

 namespace aux{ template struct seq{}; template struct gen_seq : gen_seq{}; template struct gen_seq<0, Is...> : seq{}; template void print_tuple(std::basic_ostream& os, Tuple const& t, seq){ using swallow = int[]; (void)swallow{0, (void(os < < (Is == 0? "" : ", ") << std::get(t)), 0)...}; } } // aux:: template auto operator< <(std::basic_ostream& os, std::tuple const& t) -> std::basic_ostream& { os < < "("; aux::print_tuple(os, t, aux::gen_seq()); return os < < ")"; } 

Esempio dal vivo su Ideone.


Per le informazioni sul delimitatore, aggiungi queste specializzazioni parziali:

 // Delimiters for tuple template struct delimiters, char> { static const delimiters_values values; }; template const delimiters_values delimiters, char>::values = { "(", ", ", ")" }; template struct delimiters, wchar_t> { static const delimiters_values values; }; template const delimiters_values delimiters, wchar_t>::values = { L"(", L", ", L")" }; 

e cambia l' operator< < e print_tuple conseguenza:

 template auto operator< <(std::basic_ostream& os, std::tuple const& t) -> std::basic_ostream& { typedef std::tuple tuple_t; if(delimiters::values.prefix != 0) os < < delimiters::values.prefix; print_tuple(os, t, aux::gen_seq()); if(delimiters::values.postfix != 0) os < < delimiters::values.postfix; return os; } 

E

 template void print_tuple(std::basic_ostream& os, Tuple const& t, seq){ using swallow = int[]; char const* delim = delimiters::values.delimiter; if(!delim) delim = ""; (void)swallow{0, (void(os < < (Is == 0? "" : delim) << std::get(t)), 0)...}; } 

Ho funzionato bene in C ++ 11 (gcc 4.7). Sono sicuro di alcune insidie ​​che non ho considerato, ma penso che il codice sia facile da leggere e non complicato. L’unica cosa che può essere strana è la struttura “guard” tuple_printer che garantisce che finiamo quando viene raggiunto l’ultimo elemento. L’altra cosa strana potrebbe essere sizeof … (Types) che restituisce il numero di tipi in Tipi type pack. È usato per determinare l’indice dell’ultimo elemento (dimensione … (Tipi) – 1).

 template struct tuple_printer { static void print(std::ostream& out, const Type& value) { out < < std::get(value) < < ", "; tuple_printer::print(out, value); } }; template struct tuple_printer { static void print(std::ostream& out, const Type& value) { out < < std::get(value); } }; template std::ostream& operator< <(std::ostream& out, const std::tuple& value) { out < < "("; tuple_printer, 0, sizeof...(Types) - 1>::print(out, value); out < < ")"; return out; } 

Sono sorpreso che l’implementazione di cppreference non sia già stata pubblicata qui, quindi lo farò per i posteri. È nascosto nel documento per std::tuple_cat quindi non è facile da trovare. Utilizza una struttura di guardia come alcune delle altre soluzioni qui, ma penso che la loro sia in definitiva più semplice e più facile da seguire.

 #include  #include  #include  // helper function to print a tuple of any size template struct TuplePrinter { static void print(const Tuple& t) { TuplePrinter::print(t); std::cout < < ", " << std::get(t); } }; template struct TuplePrinter { static void print(const Tuple& t) { std::cout < < std::get<0>(t); } }; template void print(const std::tuple& t) { std::cout < < "("; TuplePrinter::print(t); std::cout < < ")\n"; } // end helper function 

E una prova:

 int main() { std::tuple t1(10, "Test", 3.14); int n = 7; auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n)); n = 10; print(t2); } 

Produzione:

(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

Dimostrazione dal vivo

In C ++ 17 possiamo realizzare questo con un po ‘meno codice sfruttando le espressioni Fold , in particolare una piega unaria sinistra:

 template void print(const TupType& _tup, std::index_sequence) { std::cout < < "("; (..., (std::cout << (I == 0? "" : ", ") << std::get(_tup))); std::cout < < ")\n"; } template void print (const std::tuple& _tup) { print(_tup, std::make_index_sequence()); } 

Output dimostrativi live :

(5, Ciao, -0,1)

dato

 auto a = std::make_tuple(5, "Hello", -0.1); print(a); 

Spiegazione

La nostra piega sinistra unaria è della forma

 ... op pack 

dove op nel nostro scenario è l’operatore virgola, e pack è l’espressione che contiene la nostra tupla in un contesto non espanso come:

 (..., (std::cout < < std::get(myTuple)) 

Quindi se ho una tupla in questo modo:

 auto myTuple = std::make_tuple(5, "Hello", -0.1); 

E uno std::integer_sequence cui valori sono specificati da un modello non di tipo (vedi il codice sopra)

 size_t... I 

Quindi l’espressione

 (..., (std::cout < < std::get(myTuple)) 

Si espande in

 ((std::cout < < std::get<0>(myTuple)), (std::cout < < std::get<1>(myTuple))), (std::cout < < std::get<2>(myTuple)); 

Che stamperà

5Hello-0.1

Il che è indecente, quindi dobbiamo aggiungere qualche trucco per aggiungere un separatore di virgola da stampare prima, a meno che non sia il primo elemento.

Per fare ciò, modifichiamo la porzione del pack dell’espressione fold per stampare " ," se l’indice corrente I non è il primo, quindi il (I == 0? "" : ", ") Porzione * :

 (..., (std::cout < < (I == 0? "" : ", ") << std::get(_tup))); 

E ora ci arriveremo

5, Ciao, -0,1

Che sembra più bello (Nota: volevo un output simile a questa risposta )

* Nota: potresti eseguire la separazione delle virgole in vari modi rispetto a quello che ho ottenuto. Inizialmente ho aggiunto virgole condizionatamente dopo anziché prima testando contro std::tuple_size::value - 1 , ma era troppo lungo, quindi ho provato invece contro sizeof...(I) - 1 , ma alla fine Ho copiato Xeo e abbiamo finito con quello che ho.

Basato su esempio su The C ++ Programming Language di Bjarne Stroustrup, pagina 817 :

 #include  #include  #include  #include  template struct print_tuple{ templatestatic typename std::enable_if< (N::type print(std::ostream& os, const std::tuple& t) { char quote = (std::is_convertible(t)), std::string>::value) ? '"' : 0; os < < ", " << quote << std::get(t) < < quote; print_tuple::print(os,t); } templatestatic typename std::enable_if< !(N::type print(std::ostream&, const std::tuple&) { } }; std::ostream& operator< < (std::ostream& os, const std::tuple<>&) { return os < < "()"; } template std::ostream& operator< <(std::ostream& os, const std::tuple& t){ char quote = (std::is_convertible::value) ? '"' : 0; os < < '(' << quote << std::get<0>(t) < < quote; print_tuple<1>::print(os,t); return os < < ')'; } int main(){ std::tuple<> a; auto b = std::make_tuple("One meatball"); std::tuple c(1,1.2,"Tail!"); std::cout < < a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; } 

Produzione:

 () ("One meatball") (1, 1.2, "Tail!") 

Un altro, simile a @Tony Olsson’s, inclusa una specializzazione per la tupla vuota, come suggerito da @Kerrek SB.

 #include  #include  template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) { tuple_printer::print(out, t); if (I < sizeof...(TS)) out << ","; out << std::get(t); } }; template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) { out < < std::get<0>(t); } }; template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) {} }; template std::ostream & operator< <(std::basic_ostream & out, const std::tuple & t) { out < < "("; tuple_printer::print(out, t); return out < < ")"; }