Perhatikan pembaruan di akhir posting ini.
Pembaruan: Saya telah membuat proyek publik di GitHub untuk perpustakaan ini!
Saya ingin memiliki satu templat yang sekali dan untuk semua mengurus cukup mencetak semua wadah STL melalui operator<<
. Dalam kode semu, saya mencari sesuatu seperti ini:
template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
o << open;
// for (typename C::const_iterator i = x.begin(); i != x.end(); i++) /* Old-school */
for (auto i = x.begin(); i != x.end(); i++)
{
if (i != x.begin()) o << delim;
o << *i;
}
o << close;
return o;
}
Sekarang saya telah melihat banyak templat sulap di sini pada SO yang saya tidak pernah berpikir mungkin, jadi saya bertanya-tanya apakah ada yang bisa menyarankan sesuatu yang akan cocok dengan semua wadah C. Mungkin sesuatu sifat-ish yang dapat mengetahui apakah sesuatu memiliki iterator yang diperlukan ?
Terimakasih banyak!
Perbarui (dan solusinya)
Setelah mengangkat masalah ini lagi di Channel 9 , saya mendapat jawaban yang fantastis dari Sven Groot, yang, dikombinasikan dengan sedikit ciri tipe SFINAE, muncul untuk menyelesaikan masalah dengan cara yang sepenuhnya umum dan sederhana. Pembatas dapat dikhususkan untuk masing-masing individu, contoh spesialisasi untuk std :: set disertakan, serta contoh penggunaan pembatas khusus.
Helper "wrap_array ()" dapat digunakan untuk mencetak array C mentah. Pembaruan: Pasang dan tupel tersedia untuk dicetak; pembatas default adalah kurung bundar.
Jenis sifat enable-if membutuhkan C ++ 0x, tetapi dengan beberapa modifikasi harus dimungkinkan untuk membuat versi C ++ 98 ini. Tuples membutuhkan template variadic, karenanya C ++ 0x.
Saya telah meminta Sven untuk memposting solusi di sini sehingga saya dapat menerimanya, tetapi sementara itu saya ingin memposting kode sendiri untuk referensi. ( Perbarui: Sven sekarang telah memposting kodenya di bawah, yang saya buat jawaban yang diterima. Kode saya sendiri menggunakan sifat tipe kontainer, yang bekerja untuk saya tetapi dapat menyebabkan perilaku tak terduga dengan kelas non-kontainer yang menyediakan iterator.)
Header (prettyprint.h):
#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT
#include <type_traits>
#include <iostream>
#include <utility>
#include <tuple>
namespace std
{
// Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
template<typename T, typename TTraits, typename TAllocator> class set;
}
namespace pretty_print
{
// SFINAE type trait to detect a container based on whether T::const_iterator exists.
// (Improvement idea: check also if begin()/end() exist.)
template<typename T>
struct is_container_helper
{
private:
template<typename C> static char test(typename C::const_iterator*);
template<typename C> static int test(...);
public:
static const bool value = sizeof(test<T>(0)) == sizeof(char);
};
// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public ::std::integral_constant<bool, is_container_helper<T>::value> { };
// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
typedef TChar char_type;
const TChar * prefix;
const TChar * delimiter;
const TChar * postfix;
};
// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
typedef delimiters_values<TChar> type;
static const type values;
};
// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "[", ", ", "]" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"[", L", ", L"]" };
// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters< ::std::set<T, TTraits, TAllocator>, char>::values = { "{", ", ", "}" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };
// Delimiters for pair (reused for tuple, see below)
template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters< ::std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters< ::std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };
// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = ::std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar>>
struct print_container_helper
{
typedef TChar char_type;
typedef TDelimiters delimiters_type;
typedef std::basic_ostream<TChar, TCharTraits> & ostream_type;
print_container_helper(const T & container)
: _container(container)
{
}
inline void operator()(ostream_type & stream) const
{
if (delimiters_type::values.prefix != NULL)
stream << delimiters_type::values.prefix;
for (typename T::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it)
{
if (it != beg && delimiters_type::values.delimiter != NULL)
stream << delimiters_type::values.delimiter;
stream << *it;
}
if (delimiters_type::values.postfix != NULL)
stream << delimiters_type::values.postfix;
}
private:
const T & _container;
};
// Type-erasing helper class for easy use of custom delimiters.
// Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar.
// Usage: "cout << pretty_print::custom_delims<MyDelims>(x)".
struct custom_delims_base
{
virtual ~custom_delims_base() { }
virtual ::std::ostream & stream(::std::ostream &) = 0;
virtual ::std::wostream & stream(::std::wostream &) = 0;
};
template <typename T, typename Delims>
struct custom_delims_wrapper : public custom_delims_base
{
custom_delims_wrapper(const T & t) : t(t) { }
::std::ostream & stream(::std::ostream & stream)
{
return stream << ::pretty_print::print_container_helper<T, char, ::std::char_traits<char>, Delims>(t);
}
::std::wostream & stream(::std::wostream & stream)
{
return stream << ::pretty_print::print_container_helper<T, wchar_t, ::std::char_traits<wchar_t>, Delims>(t);
}
private:
const T & t;
};
template <typename Delims>
struct custom_delims
{
template <typename Container> custom_delims(const Container & c) : base(new custom_delims_wrapper<Container, Delims>(c)) { }
~custom_delims() { delete base; }
custom_delims_base * base;
};
} // namespace pretty_print
template <typename TChar, typename TCharTraits, typename Delims>
inline std::basic_ostream<TChar, TCharTraits> & operator<<(std::basic_ostream<TChar, TCharTraits> & stream, const pretty_print::custom_delims<Delims> & p)
{
return p.base->stream(stream);
}
// Template aliases for char and wchar_t delimiters
// Enable these if you have compiler support
//
// Implement as "template<T, C, A> const sdelims::type sdelims<std::set<T,C,A>>::values = { ... }."
//template<typename T> using pp_sdelims = pretty_print::delimiters<T, char>;
//template<typename T> using pp_wsdelims = pretty_print::delimiters<T, wchar_t>;
namespace std
{
// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream,
const ::pretty_print::print_container_helper<T, TChar, TCharTraits, TDelimiters> & helper)
{
helper(stream);
return stream;
}
// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
inline typename enable_if< ::pretty_print::is_container<T>::value, basic_ostream<TChar, TCharTraits>&>::type
operator<<(basic_ostream<TChar, TCharTraits> & stream, const T & container)
{
return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container);
}
// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const pair<T1, T2> & value)
{
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix;
stream << value.first;
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter;
stream << value.second;
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix;
return stream;
}
} // namespace std
// Prints a tuple to the stream using delimiters from delimiters<std::pair<tuple_dummy_t, tuple_dummy_t>>.
namespace pretty_print
{
struct tuple_dummy_t { }; // Just if you want special delimiters for tuples.
typedef std::pair<tuple_dummy_t, tuple_dummy_t> tuple_dummy_pair;
template<typename Tuple, size_t N, typename TChar, typename TCharTraits>
struct pretty_tuple_helper
{
static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value)
{
pretty_tuple_helper<Tuple, N - 1, TChar, TCharTraits>::print(stream, value);
if (delimiters<tuple_dummy_pair, TChar>::values.delimiter != NULL)
stream << delimiters<tuple_dummy_pair, TChar>::values.delimiter;
stream << std::get<N - 1>(value);
}
};
template<typename Tuple, typename TChar, typename TCharTraits>
struct pretty_tuple_helper<Tuple, 1, TChar, TCharTraits>
{
static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { stream << ::std::get<0>(value); }
};
} // namespace pretty_print
namespace std
{
template<typename TChar, typename TCharTraits, typename ...Args>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const tuple<Args...> & value)
{
if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix != NULL)
stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix;
::pretty_print::pretty_tuple_helper<const tuple<Args...> &, sizeof...(Args), TChar, TCharTraits>::print(stream, value);
if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix != NULL)
stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix;
return stream;
}
} // namespace std
// A wrapper for raw C-style arrays. Usage: int arr[] = { 1, 2, 4, 8, 16 }; std::cout << wrap_array(arr) << ...
namespace pretty_print
{
template <typename T, size_t N>
struct array_wrapper
{
typedef const T * const_iterator;
typedef T value_type;
array_wrapper(const T (& a)[N]) : _array(a) { }
inline const_iterator begin() const { return _array; }
inline const_iterator end() const { return _array + N; }
private:
const T * const _array;
};
} // namespace pretty_print
template <typename T, size_t N>
inline pretty_print::array_wrapper<T, N> pretty_print_array(const T (& a)[N])
{
return pretty_print::array_wrapper<T, N>(a);
}
#endif
Contoh penggunaan:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <map>
#include <set>
#include <array>
#include <tuple>
#include <utility>
#include <string>
#include "prettyprint.h"
// Specialization for a particular container
template<> const pretty_print::delimiters_values<char> pretty_print::delimiters<std::vector<double>, char>::values = { "|| ", " : ", " ||" };
// Custom delimiters for one-off use
struct MyDel { static const delimiters_values<char> values; };
const delimiters_values<char> MyDel::values = { "<", "; ", ">" };
int main(int argc, char * argv[])
{
std::string cs;
std::unordered_map<int, std::string> um;
std::map<int, std::string> om;
std::set<std::string> ss;
std::vector<std::string> v;
std::vector<std::vector<std::string>> vv;
std::vector<std::pair<int, std::string>> vp;
std::vector<double> vd;
v.reserve(argc - 1);
vv.reserve(argc - 1);
vp.reserve(argc - 1);
vd.reserve(argc - 1);
std::cout << "Printing pairs." << std::endl;
while (--argc)
{
std::string s(argv[argc]);
std::pair<int, std::string> p(argc, s);
um[argc] = s;
om[argc] = s;
v.push_back(s);
vv.push_back(v);
vp.push_back(p);
vd.push_back(1./double(i));
ss.insert(s);
cs += s;
std::cout << " " << p << std::endl;
}
std::array<char, 5> a{{ 'h', 'e', 'l', 'l', 'o' }};
std::cout << "Vector: " << v << std::endl
<< "Incremental vector: " << vv << std::endl
<< "Another vector: " << vd << std::endl
<< "Pairs: " << vp << std::endl
<< "Set: " << ss << std::endl
<< "OMap: " << om << std::endl
<< "UMap: " << um << std::endl
<< "String: " << cs << std::endl
<< "Array: " << a << std::endl
;
// Using custom delimiters manually:
std::cout << pretty_print::print_container_helper<std::vector<std::string>, char, std::char_traits<char>, MyDel>(v) << std::endl;
// Using custom delimiters with the type-erasing helper class
std::cout << pretty_print::custom_delims<MyDel>(v) << std::endl;
// Pairs and tuples and arrays:
auto a1 = std::make_pair(std::string("Jello"), 9);
auto a2 = std::make_tuple(1729);
auto a3 = std::make_tuple("Qrgh", a1, 11);
auto a4 = std::make_tuple(1729, 2875, std::pair<double, std::string>(1.5, "meow"));
int arr[] = { 1, 4, 9, 16 };
std::cout << "C array: " << wrap_array(arr) << std::endl
<< "Pair: " << a1 << std::endl
<< "1-tuple: " << a2 << std::endl
<< "n-tuple: " << a3 << std::endl
<< "n-tuple: " << a4 << std::endl
;
}
Gagasan lebih lanjut untuk peningkatan:
Menerapkan output untukPembaruan: Sekarang ini adalah pertanyaan terpisah tentang SO ! Pembaruan: Ini sekarang telah diterapkan, terima kasih kepada Xeo!std::tuple<...>
dengan cara yang sama kita memilikinya untukstd::pair<S,T>
.Tambahkan ruang nama agar kelas pembantu tidak berdarah ke ruang nama global.Selesai- Tambahkan alias templat (atau yang serupa) untuk memudahkan membuat kelas pembatas khusus, atau mungkin makro preprosesor?
Pembaruan terkini:
- Saya menghapus iterator keluaran khusus demi loop sederhana untuk fungsi cetak.
- Semua detail implementasi sekarang di
pretty_print
namespace. Hanya operator stream global danpretty_print_array
pembungkusnya ada di namespace global. - Tetap namespacing sehingga
operator<<
sekarang benar dalamstd
.
Catatan:
- Menghapus iterator keluaran berarti tidak ada cara yang digunakan
std::copy()
untuk mendapatkan pencetakan yang cantik. Saya mungkin mengembalikan iterator cantik jika ini adalah fitur yang diinginkan, tetapi kode Sven di bawah ini memiliki implementasi. - Itu adalah keputusan desain sadar untuk membuat pembatas mengkompilasi konstanta waktu daripada konstanta objek. Itu berarti bahwa Anda tidak dapat memasok pembatas secara dinamis saat runtime, tetapi itu juga berarti bahwa tidak ada overhead yang tidak dibutuhkan. Konfigurasi pembatas berbasis objek telah diusulkan oleh Dennis Zickefoose dalam komentar untuk kode Sven di bawah ini. Jika diinginkan, ini dapat diterapkan sebagai fitur alternatif.
- Saat ini tidak jelas bagaimana menyesuaikan pembatas wadah bersarang.
- Ingatlah bahwa tujuan perpustakaan ini adalah untuk memungkinkan fasilitas pencetakan kontainer cepat yang tidak memerlukan pengkodean pada bagian Anda. Ini bukan pustaka pemformatan serba guna, melainkan alat pengembangan untuk mengurangi kebutuhan untuk menulis kode pelat-ketel untuk inspeksi wadah.
Terima kasih untuk semua yang berkontribusi!
Catatan: Jika Anda mencari cara cepat untuk menggunakan pembatas kustom, berikut adalah salah satu cara menggunakan tipe penghapusan. Kami berasumsi bahwa Anda telah membangun kelas pembatas, katakanlah MyDel
, seperti ini:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Sekarang kami ingin dapat menulis std::cout << MyPrinter(v) << std::endl;
untuk beberapa wadah v
menggunakan pembatas itu. MyPrinter
akan menjadi kelas penghapus jenis, seperti:
struct wrapper_base
{
virtual ~wrapper_base() { }
virtual std::ostream & stream(std::ostream & o) = 0;
};
template <typename T, typename Delims>
struct wrapper : public wrapper_base
{
wrapper(const T & t) : t(t) { }
std::ostream & stream(std::ostream & o)
{
return o << pretty_print::print_container_helper<T, char, std::char_traits<char>, Delims>(t);
}
private:
const T & t;
};
template <typename Delims>
struct MyPrinter
{
template <typename Container> MyPrinter(const Container & c) : base(new wrapper<Container, Delims>(c)) { }
~MyPrinter() { delete base; }
wrapper_base * base;
};
template <typename Delims>
std::ostream & operator<<(std::ostream & o, const MyPrinter<Delims> & p) { return p.base->stream(o); }
sumber
pretty_print
namespace dan menyediakan pembungkus bagi pengguna untuk digunakan saat mencetak. Dari sudut pandang pengguna:std::cout << pretty_print(v);
(mungkin dengan nama yang berbeda). Kemudian Anda dapat menyediakan operator di namespace yang sama dengan pembungkusnya, dan kemudian dapat berkembang menjadi pencetakan apa pun yang Anda inginkan. Anda juga dapat meningkatkan pembungkus memungkinkan untuk menentukan pemisah opsional untuk digunakan dalam setiap panggilan (daripada menggunakan ciri-ciri yang memaksa pilihan yang sama untuk seluruh aplikasi) \Jawaban:
Solusi ini terinspirasi oleh solusi Marcelo, dengan beberapa perubahan:
Seperti versi Marcelo, ia menggunakan sifat tipe is_container yang harus dikhususkan untuk semua kontainer yang harus didukung. Dimungkinkan untuk menggunakan sifat untuk memeriksa
value_type
,const_iterator
,begin()
/end()
, tapi aku tidak yakin saya akan merekomendasikan bahwa karena itu mungkin cocok hal yang cocok dengan kriteria tersebut tetapi tidak benar-benar wadah, sepertistd::basic_string
. Juga seperti versi Marcelo, ia menggunakan templat yang dapat dikhususkan untuk menentukan pembatas yang akan digunakan.Perbedaan utama adalah bahwa saya telah membangun versi saya di sekitar a
pretty_ostream_iterator
, yang berfungsi mirip denganstd::ostream_iterator
tetapi tidak mencetak pembatas setelah item terakhir. Memformat wadah dilakukan olehprint_container_helper
, yang dapat digunakan langsung untuk mencetak wadah tanpa sifat is_container, atau untuk menentukan jenis pembatas yang berbeda.Saya juga mendefinisikan is_container dan pembatas sehingga akan berfungsi untuk kontainer dengan predikat atau pengalokasi non-standar, dan untuk char dan wchar_t. Operator << fungsi itu sendiri juga didefinisikan untuk bekerja dengan kedua char dan wchar_t stream.
Akhirnya, saya telah menggunakan
std::enable_if
, yang tersedia sebagai bagian dari C ++ 0x, dan bekerja di Visual C ++ 2010 dan g ++ 4.3 (perlu flag -st = c ++ 0x) dan kemudian. Dengan cara ini tidak ada ketergantungan pada Peningkatan.sumber
<i, j>
dalam satu fungsi dan seperti yang[i j]
lain, Anda harus mendefinisikan tipe yang sama sekali baru, dengan beberapa anggota statis untuk meneruskan tipe tersebutprint_container_helper
? Tampaknya terlalu rumit. Mengapa tidak pergi dengan objek aktual, dengan bidang yang dapat Anda atur berdasarkan kasus per kasus, dan spesialisasi hanya menyediakan nilai default yang berbeda?print_container_helper
tidak seanggun hanyaoperator<<
. Anda selalu dapat mengubah sumber, tentu saja, atau hanya menambahkan spesialisasi eksplisit untuk wadah favorit Anda, misalnya untukpair<int, int>
dan untukpair<double, string>
. Pada akhirnya ini adalah masalah menimbang kekuatan terhadap kenyamanan. Saran untuk penyambutan selamat datang!MyDels
, maka saya bisa katakanstd::cout << CustomPrinter<MyDels>(x);
. Apa yang tidak dapat saya lakukan saat ini adalah mengatakanstd::cout << CustomDelims<"{", ":", "}">(x);
, karena Anda tidak dapat memilikiconst char *
argumen templat. Keputusan untuk membuat pembatas waktu kompilasi konstan menempatkan beberapa batasan pada kemudahan penggunaan di sana, tapi saya pikir itu sangat berharga.Ini telah diedit beberapa kali, dan kami telah memutuskan untuk memanggil kelas utama yang membungkus koleksi RangePrinter
Ini harus bekerja secara otomatis dengan koleksi apa pun setelah Anda menulis operator satu kali << kelebihan, kecuali bahwa Anda akan membutuhkan yang khusus untuk peta untuk mencetak pasangan, dan mungkin ingin menyesuaikan pembatas di sana.
Anda juga dapat memiliki fungsi "cetak" khusus untuk digunakan pada item tersebut alih-alih hanya mengeluarkannya secara langsung. Sedikit seperti algoritma STL memungkinkan Anda untuk melewati predikat khusus. Dengan peta Anda akan menggunakannya seperti ini, dengan printer khusus untuk std :: pair.
Printer "default" Anda hanya akan mengeluarkannya ke aliran.
Ok, mari kita bekerja pada printer khusus. Saya akan mengubah kelas luar saya ke RangePrinter. Jadi kami memiliki 2 iterator dan beberapa pembatas tetapi belum menyesuaikan cara mencetak item yang sebenarnya.
Sekarang secara default ini akan berfungsi untuk peta selama jenis kunci dan nilai keduanya dapat dicetak dan Anda dapat memasukkan ke dalam printer item khusus Anda sendiri ketika mereka tidak (seperti yang Anda bisa dengan jenis lain), atau jika Anda tidak ingin = sebagai pembatas.
Saya sedang memindahkan fungsi-bebas untuk membuatnya hingga sekarang:
Fungsi bebas (versi iterator) akan terlihat seperti ini dan Anda bahkan dapat memiliki default:
Anda kemudian dapat menggunakannya untuk std :: atur oleh
Anda juga dapat menulis versi fungsi bebas yang menggunakan printer khusus dan yang mengambil dua iterator. Bagaimanapun mereka akan menyelesaikan parameter templat untuk Anda, dan Anda akan dapat melewatinya sebagai temporaries.
sumber
std::cout << outputFormatter(beginOfRange, endOfRange);
.std::pair
adalah contoh paling mendasar dari "koleksi dalam".std::map
dengan mudah, dan apakah itu berfungsi untuk koleksi koleksi? Saya tergoda untuk menerima yang ini sebagai jawaban. Saya harap Marcelo tidak keberatan, solusinya juga berfungsi.Berikut adalah perpustakaan yang berfungsi, disajikan sebagai program kerja yang lengkap, yang baru saja saya retas bersama:
Saat ini hanya bekerja dengan
vector
danset
, tetapi dapat dibuat untuk bekerja dengan sebagian besar wadah, hanya dengan memperluasIsContainer
spesialisasi. Saya belum terlalu memikirkan apakah kode ini minimal, tetapi saya tidak bisa segera memikirkan apa pun yang bisa saya anggap berlebihan.EDIT: Hanya untuk iseng, saya menyertakan versi yang menangani array. Saya harus mengecualikan array char untuk menghindari ambiguitas lebih lanjut; mungkin masih bermasalah
wchar_t[]
.sumber
std::map<>
dengan mengkhususkan operator, atau dengan menentukanoperator<<
untukstd::pair<>
.Delims
templat kelas!operator<<
template cocok dengan apa saja.Anda dapat memformat wadah serta rentang dan tupel menggunakan pustaka {fmt} . Sebagai contoh:
cetakan
untuk
stdout
.Penafian : Saya penulis {fmt}.
sumber
Kode terbukti berguna pada beberapa kesempatan sekarang dan saya merasa biaya untuk menyesuaikan karena penggunaannya sangat rendah. Jadi, saya memutuskan untuk merilisnya di bawah lisensi MIT dan memberikan repositori GitHub di mana header dan file contoh kecil dapat diunduh.
http://djmuw.github.io/prettycc
0. Kata Pengantar dan kata-kata
Sebuah 'hiasan' dalam hal jawaban ini adalah satu set prefix-string, pembatas-string, dan postfix-string. Di mana string awalan dimasukkan ke aliran sebelum dan string postfix setelah nilai-nilai wadah (lihat 2. Target wadah). String pembatas dimasukkan di antara nilai-nilai wadah masing-masing.
Catatan: Sebenarnya, jawaban ini tidak menjawab pertanyaan hingga 100% karena dekorasi tidak dikompilasi dengan konstan waktu karena pemeriksaan runtime diperlukan untuk memeriksa apakah dekorasi khusus telah diterapkan pada aliran saat ini. Meskipun demikian, saya pikir ini memiliki beberapa fitur yang layak.
Note2: Mungkin memiliki bug kecil karena belum diuji dengan baik.
1. Ide umum / penggunaan
Tidak ada kode tambahan yang diperlukan untuk penggunaan
Itu harus dijaga semudah
Kustomisasi yang mudah ...
... sehubungan dengan objek aliran tertentu
atau sehubungan dengan semua aliran:
Deskripsi kasar
ios_base
menggunakanxalloc
/pword
untuk menyimpan pointer kepretty::decor
objek yang secara khusus mendekorasi jenis tertentu pada aliran tertentu.Jika tidak ada
pretty::decor<T>
objek untuk aliran ini telah diatur secara eksplisitpretty::defaulted<T, charT, chartraitT>::decoration()
dipanggil untuk mendapatkan dekorasi default untuk jenis yang diberikan. Kelaspretty::defaulted
ini akan dikhususkan untuk menyesuaikan dekorasi standar.2. Menargetkan objek / wadah
Objek target
obj
untuk 'hiasan cantik' kode ini adalah objek yang memiliki keduanyastd::begin
danstd::end
didefinisikan (termasuk array C-Style),begin(obj)
danend(obj)
tersedia melalui ADL,std::tuple
std::pair
.Kode ini mencakup sifat untuk mengidentifikasi kelas dengan berbagai fitur (
begin
/end
). (Namun, tidak ada cek yang disertakan, apakahbegin(obj) == end(obj)
ekspresi yang valid.)Kode ini menyediakan
operator<<
s dalam ruang nama global yang hanya berlaku untuk kelas yang tidak memiliki versi yang lebih terspesialisasioperator<<
. Karena itu, misalnyastd::string
tidak dicetak menggunakan operator dalam kode ini walaupun memiliki yang validbegin
/end
berpasangan.3. Pemanfaatan dan kustomisasi
Dekorasi dapat dikenakan secara terpisah untuk setiap jenis (kecuali yang berbeda
tuple
) dan aliran (bukan tipe aliran!). (Yaitu astd::vector<int>
dapat memiliki dekorasi yang berbeda untuk objek aliran yang berbeda.)A) Dekorasi standar
Awalan default adalah
""
(tidak ada) seperti postfix default, sedangkan pembatas default adalah", "
(koma + spasi).B) Dekorasi standar khusus untuk suatu jenis dengan spesialisasi
pretty::defaulted
templat kelasThe
struct defaulted
memiliki fungsi anggota statisdecoration()
mengembalikandecor
objek yang mencakup nilai-nilai default untuk jenis tertentu.Contoh menggunakan array:
Kustomisasi pencetakan array standar:
Cetak array arry:
Menggunakan
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
makro untukchar
streamingMakro diperluas ke
memungkinkan spesialisasi parsial di atas untuk ditulis ulang
atau memasukkan spesialisasi penuh seperti
Makro lain untuk
wchar_t
aliran disertakan:PRETTY_DEFAULT_WDECORATION
.C) Kenakan hiasan pada aliran
Fungsi
pretty::decoration
ini digunakan untuk memaksakan dekorasi pada aliran tertentu. Ada kelebihan mengambil salah satu - argumen string menjadi pembatas (mengadopsi awalan dan postfix dari kelas default) - atau tiga argumen string menyusun dekorasi lengkapDekorasi lengkap untuk jenis dan aliran tertentu
Kustomisasi pembatas untuk aliran yang diberikan
4. Penanganan khusus
std::tuple
Alih-alih memungkinkan spesialisasi untuk setiap jenis tuple yang mungkin, kode ini berlaku untuk dekorasi apa pun yang tersedia untuk
std::tuple<void*>
semua jenisstd::tuple<...>
.5. Hapus dekorasi khusus dari aliran
Untuk kembali ke dekorasi default untuk tipe tertentu gunakan
pretty::clear
template fungsi pada streams
.5. Contoh lebih lanjut
Mencetak "seperti matriks" dengan pembatas baris baru
Cetakan
Lihat di ideone / KKUebZ
6. Kode
sumber
Saya akan menambahkan jawaban lain di sini, karena saya telah datang dengan pendekatan yang berbeda dengan yang sebelumnya, dan itu adalah dengan menggunakan aspek lokal.
Dasar-dasarnya ada di sini
Pada dasarnya apa yang Anda lakukan adalah:
std::locale::facet
. Kelemahan sedikit adalah bahwa Anda akan memerlukan unit kompilasi di suatu tempat untuk menyimpan id-nya. Sebut saja MyPrettyVectorPrinter. Anda mungkin memberinya nama yang lebih baik, dan juga membuat yang untuk pasangan dan peta.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
operator<<
) menyediakan yang standar. Catatan Anda dapat melakukan hal yang sama untuk membaca vektor.Saya suka metode ini karena Anda dapat menggunakan cetakan default sementara masih dapat menggunakan penggantian kustom.
Kerugiannya membutuhkan perpustakaan untuk sisi Anda jika digunakan dalam beberapa proyek (jadi tidak bisa hanya header-saja) dan juga fakta bahwa Anda perlu berhati-hati tentang biaya membuat objek lokal baru.
Saya telah menulis ini sebagai solusi baru daripada memodifikasi yang lain karena saya percaya kedua pendekatan bisa benar dan Anda pilih.
sumber
Tujuannya di sini adalah menggunakan ADL untuk melakukan kustomisasi tentang cara kami mencetak dengan cantik.
Anda melewatkan tag formatter, dan menimpa 4 fungsi (sebelum, sesudah, antara dan turun) di namespace tag. Ini mengubah cara formatter mencetak 'perhiasan' saat iterasi pada wadah.
Pemformat default yang berfungsi
{(a->b),(c->d)}
untuk peta,(a,b,c)
untuk tupleoids,"hello"
untuk string,[x,y,z]
untuk semua yang lain disertakan.Itu harus "hanya bekerja" dengan tipe iterable pihak ke-3 (dan memperlakukan mereka seperti "yang lainnya").
Jika Anda ingin perhiasan khusus untuk item pihak ketiga Anda, cukup buat tag Anda sendiri. Butuh sedikit usaha untuk menangani penurunan peta (Anda harus kelebihan
pretty_print_descend( your_tag
untuk kembalipretty_print::decorator::map_magic_tag<your_tag>
). Mungkin ada cara yang lebih bersih untuk melakukan ini, tidak yakin.Perpustakaan kecil untuk mendeteksi iterability, dan tuple-ness:
Perpustakaan yang memungkinkan kami mengunjungi konten objek jenis iterable atau tuple:
Perpustakaan pencetakan yang cantik:
Kode uji:
contoh hidup
Ini memang menggunakan fitur C ++ 14 (beberapa
_t
alias, danauto&&
lambdas), tetapi tidak ada yang penting.sumber
->
dalampair
s darimap
s) pada saat ini. Inti dari perpustakaan cetak yang cantik itu bagus dan kecil, yang bagus. Saya mencoba membuatnya mudah diperluas, tidak yakin apakah saya berhasil.Solusi saya simple.h , yang merupakan bagian dari paket scc . Semua wadah std, peta, set, c-array dapat dicetak.
sumber
i
?std::set
dengan komparator kustom, atau unordered_map dengan kesetaraan khusus. Akan sangat penting untuk mendukung konstruksi tersebut.Keluar dari salah satu BoostCon pertama (sekarang disebut CppCon), saya dan dua orang lainnya bekerja di perpustakaan untuk melakukan hal ini. Poin utama yang perlu diperhatikan adalah memperpanjang namespace std. Itu ternyata menjadi larangan untuk meningkatkan perpustakaan.
Sayangnya tautan ke kode tidak lagi berfungsi, tetapi Anda mungkin menemukan beberapa informasi menarik dalam diskusi (setidaknya yang tidak membicarakan tentang apa nama itu!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
sumber
Ini adalah versi implementasi saya yang dilakukan pada tahun 2016
Semuanya dalam satu tajuk, jadi mudah digunakan https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
sumber