Ini adalah tindak lanjut dari pertanyaan saya sebelumnya tentang wadah STL dengan pencetakan cantik , yang untuknya kami berhasil mengembangkan solusi yang sangat elegan dan sepenuhnya umum.
Pada langkah selanjutnya, saya ingin memasukkan pencetakan cantik untuk std::tuple<Args...>
, menggunakan templat variadic (jadi ini benar-benar C ++ 11). Karena std::pair<S,T>
, saya hanya mengatakan
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
return o << "(" << p.first << ", " << p.second << ")";
}
Apa konstruksi analog untuk mencetak tupel?
Saya telah mencoba berbagai bit argumen template yang membongkar tumpukan, meneruskan indeks dan menggunakan SFINAE untuk mengetahui kapan saya berada di elemen terakhir, tetapi tidak berhasil. Saya tidak akan membebani Anda dengan kode saya yang rusak; deskripsi masalahnya semoga cukup lurus ke depan. Pada dasarnya, saya menyukai perilaku berikut:
auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)
Poin bonus untuk menyertakan tingkat umum yang sama (char / wchar_t, pembatas pasangan) seperti pertanyaan sebelumnya!
sumber
Jawaban:
Yay, indeks ~
namespace aux{ template<std::size_t...> struct seq{}; template<std::size_t N, std::size_t... Is> struct gen_seq : gen_seq<N-1, N-1, Is...>{}; template<std::size_t... Is> struct gen_seq<0, Is...> : seq<Is...>{}; 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)...}; } } // aux:: template<class Ch, class Tr, class... Args> auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) -> std::basic_ostream<Ch, Tr>& { os << "("; aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>()); return os << ")"; }
Contoh langsung di Ideone.
Untuk pembatas, cukup tambahkan spesialisasi parsial ini:
// Delimiters for tuple template<class... Args> struct delimiters<std::tuple<Args...>, char> { static const delimiters_values<char> values; }; template<class... Args> const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = { "(", ", ", ")" }; template<class... Args> struct delimiters<std::tuple<Args...>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<class... Args> const delimiters_values<wchar_t> delimiters<std::tuple<Args...>, wchar_t>::values = { L"(", L", ", L")" };
dan ubah
operator<<
danprint_tuple
sesuai:template<class Ch, class Tr, class... Args> auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) -> std::basic_ostream<Ch, Tr>& { typedef std::tuple<Args...> tuple_t; if(delimiters<tuple_t, Ch>::values.prefix != 0) os << delimiters<tuple_t,char>::values.prefix; print_tuple(os, t, aux::gen_seq<sizeof...(Args)>()); if(delimiters<tuple_t, Ch>::values.postfix != 0) os << delimiters<tuple_t,char>::values.postfix; return os; }
Dan
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[]; char const* delim = delimiters<Tuple, Ch>::values.delimiter; if(!delim) delim = ""; (void)swallow{0, (void(os << (Is == 0? "" : delim) << std::get<Is>(t)), 0)...}; }
sumber
TuplePrinter
tidak memilikioperator<<
.class Tuple
untukoperator<<
kelebihan beban - itu akan diambil untuk semua hal. Ini akan membutuhkan batasan, yang menyiratkan perlunya beberapa jenis argumen variadic.swallow{(os << get<Is>(t))...};
.Di C ++ 17 kita dapat melakukannya dengan sedikit kode dengan memanfaatkan ekspresi Fold , terutama lipatan kiri unary:
template<class TupType, size_t... I> void print(const TupType& _tup, std::index_sequence<I...>) { std::cout << "("; (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup))); std::cout << ")\n"; } template<class... T> void print (const std::tuple<T...>& _tup) { print(_tup, std::make_index_sequence<sizeof...(T)>()); }
Keluaran Demo Langsung :
diberikan
auto a = std::make_tuple(5, "Hello", -0.1); print(a);
Penjelasan
Lipatan kiri unary kita berbentuk
op
di mana dalam skenario kami adalah operator koma, danpack
merupakan ekspresi yang berisi tupel kami dalam konteks yang tidak diperluas seperti:(..., (std::cout << std::get<I>(myTuple))
Jadi jika saya punya tupel seperti ini:
auto myTuple = std::make_tuple(5, "Hello", -0.1);
Dan
std::integer_sequence
yang nilainya ditentukan oleh template non-tipe (lihat kode di atas)size_t... I
Lalu ekspresinya
(..., (std::cout << std::get<I>(myTuple))
Akan diperluas menjadi
((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));
Yang akan dicetak
Itu kotor, jadi kita perlu melakukan beberapa trik lagi untuk menambahkan pemisah koma untuk dicetak terlebih dahulu kecuali itu adalah elemen pertama.
Untuk mencapai itu, kami memodifikasi
pack
bagian ekspresi lipatan untuk dicetak" ,"
jika indeks saatI
ini bukan yang pertama, karenanya(I == 0? "" : ", ")
bagian * :(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
Dan sekarang kita akan mendapatkannya
Yang terlihat lebih bagus (Catatan: Saya ingin keluaran yang serupa dengan jawaban ini )
* Catatan: Anda dapat melakukan pemisahan koma dengan berbagai cara daripada yang saya lakukan. Saya awalnya menambahkan koma secara bersyarat setelah daripada sebelumnya dengan menguji
std::tuple_size<TupType>::value - 1
, tapi itu terlalu lama, jadi saya mengujinyasizeof...(I) - 1
, tetapi pada akhirnya saya menyalin Xeo dan kami berakhir dengan apa yang saya dapatkan.sumber
if constexpr
untuk kasus dasar.Saya mendapatkan ini berfungsi dengan baik di C ++ 11 (gcc 4.7). Saya yakin ada beberapa jebakan yang belum saya pertimbangkan tetapi menurut saya kodenya mudah dibaca dan tidak rumit. Satu-satunya hal yang mungkin aneh adalah "penjaga" struct tuple_printer yang memastikan bahwa kita menghentikan ketika elemen terakhir tercapai. Hal aneh lainnya mungkin sizeof ... (Tipe) yang mengembalikan jumlah tipe dalam Tipe tipe paket. Ini digunakan untuk menentukan indeks elemen terakhir (ukuran ... (Jenis) - 1).
template<typename Type, unsigned N, unsigned Last> struct tuple_printer { static void print(std::ostream& out, const Type& value) { out << std::get<N>(value) << ", "; tuple_printer<Type, N + 1, Last>::print(out, value); } }; template<typename Type, unsigned N> struct tuple_printer<Type, N, N> { static void print(std::ostream& out, const Type& value) { out << std::get<N>(value); } }; template<typename... Types> std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) { out << "("; tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value); out << ")"; return out; }
sumber
std::make_tuple()
. tetapi pada saat mencetaknyamain()
, ada banyak kesalahan !, Adakah saran tentang cara yang lebih sederhana untuk mencetak tupel?Saya terkejut implementasi di cppreference belum diposting di sini, jadi saya akan melakukannya untuk anak cucu. Itu disembunyikan di dokumen untuk
std::tuple_cat
jadi tidak mudah ditemukan. Ini menggunakan struktur penjaga seperti beberapa solusi lain di sini, tetapi saya pikir solusi mereka pada akhirnya lebih sederhana dan lebih mudah diikuti.#include <iostream> #include <tuple> #include <string> // helper function to print a tuple of any size template<class Tuple, std::size_t N> struct TuplePrinter { static void print(const Tuple& t) { TuplePrinter<Tuple, N-1>::print(t); std::cout << ", " << std::get<N-1>(t); } }; template<class Tuple> struct TuplePrinter<Tuple, 1> { static void print(const Tuple& t) { std::cout << std::get<0>(t); } }; template<class... Args> void print(const std::tuple<Args...>& t) { std::cout << "("; TuplePrinter<decltype(t), sizeof...(Args)>::print(t); std::cout << ")\n"; } // end helper function
Dan tes:
int main() { std::tuple<int, std::string, float> 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); }
Keluaran:
Demo Langsung
sumber
Berdasarkan kode AndyG, untuk C ++ 17
#include <iostream> #include <tuple> template<class TupType, size_t... I> std::ostream& tuple_print(std::ostream& os, const TupType& _tup, std::index_sequence<I...>) { os << "("; (..., (os << (I == 0 ? "" : ", ") << std::get<I>(_tup))); os << ")"; return os; } template<class... T> std::ostream& operator<< (std::ostream& os, const std::tuple<T...>& _tup) { return tuple_print(os, _tup, std::make_index_sequence<sizeof...(T)>()); } int main() { std::cout << "deep tuple: " << std::make_tuple("Hello", 0.1, std::make_tuple(1,2,3,"four",5.5), 'Z') << std::endl; return 0; }
dengan keluaran:
deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
sumber
Berdasarkan contoh pada The C ++ Programming Language By Bjarne Stroustrup, halaman 817 :
#include <tuple> #include <iostream> #include <string> #include <type_traits> template<size_t N> struct print_tuple{ template<typename... T>static typename std::enable_if<(N<sizeof...(T))>::type print(std::ostream& os, const std::tuple<T...>& t) { char quote = (std::is_convertible<decltype(std::get<N>(t)), std::string>::value) ? '"' : 0; os << ", " << quote << std::get<N>(t) << quote; print_tuple<N+1>::print(os,t); } template<typename... T>static typename std::enable_if<!(N<sizeof...(T))>::type print(std::ostream&, const std::tuple<T...>&) { } }; std::ostream& operator<< (std::ostream& os, const std::tuple<>&) { return os << "()"; } template<typename T0, typename ...T> std::ostream& operator<<(std::ostream& os, const std::tuple<T0, T...>& t){ char quote = (std::is_convertible<T0, std::string>::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<int,double,std::string> c(1,1.2,"Tail!"); std::cout << a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; }
Keluaran:
() ("One meatball") (1, 1.2, "Tail!")
sumber
Memanfaatkan
std::apply
(C ++ 17) kita dapat menjatuhkanstd::index_sequence
dan mendefinisikan satu fungsi:#include <tuple> #include <iostream> template<class Ch, class Tr, class... Args> auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) { std::apply([&os](auto&&... args) {((os << args << " "), ...);}, t); return os; }
Atau, sedikit dibumbui dengan bantuan stringstream:
#include <tuple> #include <iostream> #include <sstream> template<class Ch, class Tr, class... Args> auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) { std::basic_stringstream<Ch, Tr> ss; ss << "[ "; std::apply([&ss](auto&&... args) {((ss << args << ", "), ...);}, t); ss.seekp(-2, ss.cur); ss << " ]"; return os << ss.str(); }
sumber
Satu lagi, mirip dengan @Tony Olsson, termasuk spesialisasi untuk tupel kosong, seperti yang disarankan oleh @Kerrek SB.
#include <tuple> #include <iostream> template<class Ch, class Tr, size_t I, typename... TS> struct tuple_printer { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { tuple_printer<Ch, Tr, I-1, TS...>::print(out, t); if (I < sizeof...(TS)) out << ","; out << std::get<I>(t); } }; template<class Ch, class Tr, typename... TS> struct tuple_printer<Ch, Tr, 0, TS...> { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { out << std::get<0>(t); } }; template<class Ch, class Tr, typename... TS> struct tuple_printer<Ch, Tr, -1, TS...> { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) {} }; template<class Ch, class Tr, typename... TS> std::ostream & operator<<(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { out << "("; tuple_printer<Ch, Tr, sizeof...(TS) - 1, TS...>::print(out, t); return out << ")"; }
sumber
Saya suka jawaban DarioP, tetapi stringstream menggunakan heap. Ini bisa dihindari:
template <class... Args> std::ostream& operator<<(std::ostream& os, std::tuple<Args...> const& t) { os << "("; bool first = true; std::apply([&os, &first](auto&&... args) { auto print = [&] (auto&& val) { if (!first) os << ","; (os << " " << val); first = false; }; (print(args), ...); }, t); os << " )"; return os; }
sumber
Satu hal yang saya tidak suka tentang jawaban sebelumnya yang menggunakan ekspresi lipatan adalah bahwa mereka menggunakan urutan indeks atau bendera untuk melacak elemen pertama, yang menghilangkan banyak manfaat dari ekspresi lipatan bersih yang bagus.
Berikut adalah contoh yang tidak perlu diindeks, tetapi mendapatkan hasil yang serupa. (Tidak secanggih beberapa yang lain, tetapi lebih banyak yang bisa ditambahkan.)
Tekniknya adalah menggunakan apa yang sudah diberikan lipatan: kasing khusus untuk satu elemen. Yaitu, satu elemen lipatan hanya mengembang
elem[0]
, lalu 2 elemenelem[0] + elem[1]
, di mana+
beberapa operasi. Yang kami inginkan adalah satu elemen menulis hanya elemen itu ke aliran, dan untuk lebih banyak elemen, lakukan hal yang sama, tetapi gabungkan masing-masing dengan tambahan penulisan ",". Jadi memetakan ini ke lipatan c ++, kami ingin setiap elemen menjadi tindakan menulis beberapa objek ke aliran. Kami ingin+
operasi kami menyelingi dua penulisan dengan "," tulis. Jadi pertama-tama ubah urutan tupel kita menjadi urutan tindakan tulis,CommaJoiner
saya menyebutnya, lalu untuk tindakan itu tambahkanoperator+
untuk menggabungkan dua tindakan seperti yang kita inginkan, menambahkan "," di antara:#include <tuple> #include <iostream> template <typename T> struct CommaJoiner { T thunk; explicit CommaJoiner(const T& t) : thunk(t) {} template <typename S> auto operator+(CommaJoiner<S> const& b) const { auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) { a(os); os << ", "; b(os); }; return CommaJoiner<decltype(joinedThunk)>{joinedThunk}; } void operator()(std::ostream& os) const { thunk(os); } }; template <typename ...Ts> std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup) { std::apply([&](auto ...ts) { return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os); return os; } int main() { auto tup = std::make_tuple(1, 2.0, "Hello"); std::cout << tup << std::endl; }
Sekilas pandang pada godbolt menunjukkan bahwa ini terkompilasi dengan cukup baik juga, semua panggilan thunks diratakan.
Ini akan membutuhkan kelebihan kedua untuk menangani tupel kosong.
sumber
Berikut beberapa kode yang baru-baru ini saya buat untuk mencetak tupel.
#include <iostream> #include <tuple> using namespace std; template<typename... Ts> ostream& operator<<(ostream& output, const tuple<Ts...> t) { output << '('; apply([&](auto&&... args) { ((cout << args << ", "), ...); }, t); output << "\b\b"; output << ')'; return output; }
Menggunakan contoh kasus Anda:
auto a = std::make_tuple(5, "Hello", -0.1); cout << a << '\n'; // (5, Hello, -0.1)
sumber