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);
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
, 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::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!")
ed ecco un’altra implementazione:
https://github.com/galaxyeye/atlas/blob/master/atlas/io/tuple.h
con il codice di prova:
https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/tuple.cpp
godere 🙂
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 << ")"; }