Zwróć uwagę na aktualizacje na końcu tego postu.
Aktualizacja: Utworzyłem projekt publiczny na GitHub dla tej biblioteki!
Chciałbym mieć jeden szablon, który raz na zawsze zajmuje się ładnym drukowaniem wszystkich pojemników STL operator<<
. W pseudokodzie szukam czegoś takiego:
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;
}
Teraz widziałem mnóstwo magii szablonów tutaj na SO, więc nigdy nie myślałem, że to możliwe, więc zastanawiam się, czy ktoś może zasugerować coś, co pasowałoby do wszystkich pojemników C. Może coś cechującego, które może dowiedzieć się, czy coś ma niezbędną iterację ?
Wielkie dzięki!
Aktualizacja (i rozwiązanie)
Po ponownym poruszeniu tego problemu na kanale 9 otrzymałem fantastyczną odpowiedź od Svena Groota, który w połączeniu z odrobiną cech typu SFINAE wydaje się rozwiązać problem w sposób całkowicie ogólny i możliwy do zagnieżdżenia. Ograniczniki mogą być indywidualnie specjalizowane, dołączona jest przykładowa specjalizacja dla std :: set, a także przykład użycia niestandardowych ograniczników.
Pomocnika „wrap_array ()” można używać do drukowania surowych tablic C. Aktualizacja: Pary i krotki są dostępne do drukowania; domyślnymi ogranicznikami są nawiasy okrągłe.
Funkcja typu „włącz-jeśli” wymaga C ++ 0x, ale z pewnymi modyfikacjami powinna istnieć możliwość stworzenia wersji C ++ 98. Krotki wymagają różnych szablonów, stąd C ++ 0x.
Poprosiłem Svena o opublikowanie tutaj rozwiązania, aby je zaakceptować, ale tymczasem sam chciałbym opublikować kod w celach informacyjnych. ( Aktualizacja: Sven opublikował teraz swój kod poniżej, na który otrzymałem zaakceptowaną odpowiedź. Mój własny kod wykorzystuje cechy typu kontenera, które działają dla mnie, ale mogą powodować nieoczekiwane zachowanie w przypadku klas innych niż kontener, które zapewniają iteratory).
Nagłówek (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
Przykład użycia:
#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
;
}
Dalsze pomysły na ulepszenia:
Zaimplementuj dane wyjścioweAktualizacja: To jest teraz osobne pytanie dotyczące SO ! Aktualizacja: Zostało to zaimplementowane dzięki Xeo!std::tuple<...>
w taki sam sposób, w jaki mamy jestd::pair<S,T>
.Dodaj przestrzenie nazw, aby klasy pomocnicze nie przedostawały się do globalnej przestrzeni nazw.Gotowy- Dodać aliasy szablonów (lub coś podobnego), aby ułatwić tworzenie niestandardowych klas separatora, a może makra preprocesora?
Ostatnie aktualizacje:
- Usunąłem niestandardowy iterator wyjściowy na rzecz prostej pętli for w funkcji drukowania.
- Wszystkie szczegóły implementacji znajdują się teraz w
pretty_print
przestrzeni nazw. Tylko globalni operatorzy strumienia ipretty_print_array
opakowanie znajdują się w globalnej przestrzeni nazw. - Naprawiono przestrzeń nazw, dzięki czemu
operator<<
jest teraz poprawnie wstd
.
Uwagi:
- Usunięcie iteratora wyjściowego oznacza, że nie ma sposobu
std::copy()
na uzyskanie ładnego wydruku. Mogę przywrócić ładny iterator, jeśli jest to pożądana funkcja, ale poniższy kod Svena ma implementację. - Decyzja o ograniczeniu czasowym kompilacji stała stała się świadomą decyzją projektową, a nie stałą obiektu. Oznacza to, że nie można dynamicznie dostarczać ograniczników w czasie wykonywania, ale oznacza to również, że nie ma niepotrzebnych kosztów ogólnych. Dennis Zickefoose zaproponował obiektową konfigurację separatora w komentarzu do kodu Svena poniżej. W razie potrzeby można to zaimplementować jako funkcję alternatywną.
- Obecnie nie jest oczywiste, jak dostosować zagnieżdżone ograniczniki kontenerów.
- Należy pamiętać, że celem tej biblioteki jest umożliwienie szybkiego drukowania pojemników, które wymaga zerowego kodowania z Twojej strony. Nie jest to uniwersalna biblioteka formatująca, ale raczej narzędzie programistyczne, które zmniejsza potrzebę pisania kodu płyty kotłowej do kontroli kontenera.
Dziękujemy wszystkim, którzy przyczynili się!
Uwaga: jeśli szukasz szybkiego sposobu wdrożenia niestandardowych ograniczników, oto jeden ze sposobów usuwania typu. Zakładamy, że zbudowałeś już klasę separatora, powiedzmy MyDel
tak:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Teraz chcemy mieć możliwość pisania std::cout << MyPrinter(v) << std::endl;
dla niektórych kontenerów v
przy użyciu tych ograniczników. MyPrinter
będzie klasą kasującą typy, taką jak:
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); }
źródło
pretty_print
przestrzeni nazw i zapewnienie opakowania dla użytkownika do użycia podczas drukowania. Z punktu widzenia użytkownika:std::cout << pretty_print(v);
(prawdopodobnie pod inną nazwą). Następnie możesz podać operatora w tej samej przestrzeni nazw, co opakowanie, a następnie można go rozszerzyć, aby wydrukować wszystko, co chcesz. Możesz także ulepszyć opakowanie, umożliwiając opcjonalne zdefiniowanie separatora do użycia w ramach każdego połączenia (zamiast używania cech, które wymuszają taki sam wybór dla całej aplikacji) \Odpowiedzi:
To rozwiązanie zostało zainspirowane rozwiązaniem Marcelo, z kilkoma zmianami:
Podobnie jak wersja Marcelo, wykorzystuje on cechę typu is_container, która musi być wyspecjalizowana dla wszystkich obsługiwanych kontenerów. Może to być możliwe użycie cechę, aby sprawdzić
value_type
,const_iterator
,begin()
/end()
, ale nie jestem pewien, polecam, że skoro może dopasować rzeczy, które pasują do tych kryteriów, ale w rzeczywistości nie są pojemniki, jakstd::basic_string
. Podobnie jak wersja Marcelo, wykorzystuje szablony, które można wyspecjalizować, aby określić ograniczniki do użycia.Główną różnicą jest to, że zbudowałem swoją wersję wokół
pretty_ostream_iterator
, który działa podobnie do tego,std::ostream_iterator
ale nie drukuje ogranicznika po ostatnim elemencie. Formatowanie kontenerów odbywa się za pomocą poleceniaprint_container_helper
, którego można użyć bezpośrednio do drukowania kontenerów bez cechy is_container lub do określania innego typu separatora.Zdefiniowałem również is_container i ograniczniki, więc będzie działać dla kontenerów z niestandardowymi predykatami lub alokatorami, a także dla char i wchar_t. Sama funkcja operatora << jest również zdefiniowana do pracy ze strumieniami char i wchar_t.
Wreszcie użyłem
std::enable_if
, który jest dostępny jako część C ++ 0x i działa w Visual C ++ 2010 i g ++ 4.3 (wymaga flagi -std = c ++ 0x) i późniejszych. W ten sposób nie ma zależności od wzmocnienia.źródło
<i, j>
w jednej funkcji, a tak jak[i j]
w innej, musisz zdefiniować zupełnie nowy typ, z garstką statycznych elementów, aby przekazać ten typprint_container_helper
? To wydaje się zbyt skomplikowane. Dlaczego nie wybrać rzeczywistego obiektu z polami, które można ustawić indywidualnie dla każdego przypadku, a specjalizacje po prostu zapewniają różne wartości domyślne?print_container_helper
nie jest tak eleganckie jak tylkooperator<<
. Zawsze możesz oczywiście zmienić źródło lub po prostu dodać wyraźne specjalizacje dla swojego ulubionego kontenera, np. Dlapair<int, int>
i dlapair<double, string>
. Ostatecznie chodzi o porównanie siły z wygodą. Sugestie dotyczące ulepszeń mile widziane!MyDels
, to mogę powiedziećstd::cout << CustomPrinter<MyDels>(x);
. Co mi nie może zrobić w tej chwili, to powiedziećstd::cout << CustomDelims<"{", ":", "}">(x);
, ponieważ nie można miećconst char *
argumenty szablonu. Decyzja o ustawieniu stałej czasowej kompilacji ograniczników nakłada pewne ograniczenia na łatwość użycia, ale myślę, że warto.Zostało to edytowane kilka razy i postanowiliśmy nazwać główną klasę, która otacza kolekcję RangePrinter
Powinno to działać automatycznie z dowolną kolekcją po napisaniu przeciążenia operatora << <<, z tym wyjątkiem, że będziesz potrzebować specjalnej mapy do wydrukowania pary i możesz tam dostosować ogranicznik.
Możesz również mieć specjalną funkcję „drukowania”, która będzie używana na elemencie, a nie tylko na bezpośrednim wyświetlaniu go. Trochę jak algorytmy STL pozwalają przekazywać niestandardowe predykaty. Z mapą użyłbyś jej w ten sposób, z niestandardową drukarką dla std :: pair.
Twoja „domyślna” drukarka po prostu wyśle go do strumienia.
Ok, pracujmy na niestandardowej drukarce. Zmienię moją zewnętrzną klasę na RangePrinter. Mamy więc 2 iteratory i niektóre ograniczniki, ale nie dostosowaliśmy sposobu drukowania rzeczywistych elementów.
Teraz domyślnie będzie działać na mapach, o ile typy klucza i wartości można drukować i można wstawić własną drukarkę przedmiotów specjalnych, gdy nie są (tak jak w przypadku dowolnego innego typu) lub jeśli nie chcesz = jako separator.
Przenoszę teraz funkcję swobodną, aby utworzyć je do końca:
Darmowa funkcja (wersja iteratora) wyglądałaby tak, a można nawet mieć wartości domyślne:
Możesz użyć go do std :: set by
Możesz także napisać wersję bezpłatną, która zajmuje niestandardową drukarkę i taką, która wymaga dwóch iteratorów. W każdym razie rozwiążą one parametry szablonu i będziesz mógł przekazać je jako tymczasowe.
źródło
std::cout << outputFormatter(beginOfRange, endOfRange);
.std::pair
jest najbardziej podstawowym przykładem „kolekcji wewnętrznej”.std::map
i czy działa w przypadku kolekcji zbiorów? Kusi mnie jednak, by przyjąć tę odpowiedź. Mam nadzieję, że Marcelo nie ma nic przeciwko, jego rozwiązanie również działa.Oto działająca biblioteka, przedstawiona jako kompletny działający program, który właśnie zhakowałem razem:
Obecnie działa tylko z
vector
iset
, ale może być przystosowany do pracy z większością kontenerów, po prostu rozszerzającIsContainer
specjalizacje. Nie zastanawiałem się zbytnio, czy ten kod jest minimalny, ale nie mogę od razu wymyślić niczego, co można by uznać za zbędne.EDYCJA: Tylko dla kopnięć, zawarłem wersję, która obsługuje tablice. Musiałem wykluczyć tablice znaków, aby uniknąć dalszych dwuznaczności; wciąż może mieć kłopoty
wchar_t[]
.źródło
std::map<>
specjalizując się z operatorem lub definiującoperator<<
forstd::pair<>
.Delims
szablonu klasy!operator<<
szablon pasuje prawie do wszystkiego.Możesz formatować kontenery oraz zakresy i krotki za pomocą biblioteki {fmt} . Na przykład:
odciski
do
stdout
.Oświadczenie : Jestem autorem {fmt}.
źródło
Kod okazał się przydatny przy kilku okazjach i wydaje mi się, że koszt dostosowania się do potrzeb jest niewielki. Dlatego postanowiłem wydać go na licencji MIT i udostępnić repozytorium GitHub, w którym można pobrać nagłówek i mały przykładowy plik.
http://djmuw.github.io/prettycc
0. Przedmowa i sformułowania
A 'dekoracji' pod względem tej odpowiedzi jest zbiorem prefix-ciąg, separator-string, a postfix-string. Tam, gdzie ciąg przedrostka jest wstawiany do strumienia przed, a łańcuch po przedrostku za wartościami kontenera (patrz 2. Docelowe kontenery). Ciąg separatora jest wstawiany między wartościami odpowiedniego kontenera.
Uwaga: W rzeczywistości ta odpowiedź nie odnosi się do pytania w 100%, ponieważ dekoracja nie jest ściśle skompilowaną stałą czasową, ponieważ wymagane są kontrole środowiska wykonawczego w celu sprawdzenia, czy do bieżącego strumienia zastosowano dekorację niestandardową. Niemniej jednak myślę, że ma kilka przyzwoitych funkcji.
Uwaga 2: Mogą zawierać drobne błędy, ponieważ nie zostały jeszcze dobrze przetestowane.
1. Ogólny pomysł / zastosowanie
Zero dodatkowy kod wymagany do użycia
To powinno być tak proste, jak
Łatwa personalizacja ...
... w odniesieniu do konkretnego obiektu strumienia
lub w odniesieniu do wszystkich strumieni:
Szorstki opis
ios_base
użyciexalloc
/pword
w celu zapisania wskaźnika dopretty::decor
obiektu, który konkretnie dekoruje określony typ w określonym strumieniu.Jeśli żaden
pretty::decor<T>
obiekt dla tego strumienia nie został jawniepretty::defaulted<T, charT, chartraitT>::decoration()
wywołany, w celu uzyskania domyślnej dekoracji dla danego typu. Ta klasapretty::defaulted
ma się specjalizować w dostosowywaniu domyślnych dekoracji.2. Docelowe obiekty / kontenery
Obiektami docelowymi
obj
dla „ładnej dekoracji” tego kodu są obiekty posiadające jedno z nichstd::begin
istd::end
zdefiniowane (obejmuje tablice typu C),begin(obj)
iend(obj)
dostępne za pośrednictwem ADL,std::tuple
std::pair
.Kod zawiera cechę służącą do identyfikacji klas z funkcjami zakresu (
begin
/end
). (Nie ma jednak żadnej kontroli, czybegin(obj) == end(obj)
jest to prawidłowe wyrażenie.)Kod udostępnia
operator<<
s w globalnej przestrzeni nazw, która ma zastosowanie tylko do klas, które nie mają bardziej wyspecjalizowanej wersjioperator<<
. Dlatego na przykładstd::string
nie jest drukowany przy użyciu operatora w tym kodzie, chociaż ma prawidłowąbegin
/end
parę.3. Wykorzystanie i dostosowanie
Dekoracje można nakładać osobno dla każdego typu (z wyjątkiem różnych
tuple
s) i strumienia (nie typu strumienia!). (Tjstd::vector<int>
Może mieć różne dekoracje dla różnych obiektów strumienia).A) Domyślna dekoracja
Domyślnym prefiksem jest
""
(nic), podobnie jak domyślny postfiks, a domyślnym separatorem jest", "
(przecinek + spacja).B) Dostosowana domyślna dekoracja typu poprzez specjalizację
pretty::defaulted
szablonu klasystruct defaulted
Ma statycznej funkcji składowejdecoration()
przekazującychdecor
obiekt, który zawiera wartości domyślne dla danego typu.Przykład użycia tablicy:
Dostosuj domyślne drukowanie tablic:
Wydrukuj tablicę arry:
Korzystanie z
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
makra dlachar
strumieniMakro rozwija się do
umożliwiając przepisanie powyższej częściowej specjalizacji
lub wstawienie pełnej specjalizacji, takiej jak
Innym makro dla
wchar_t
strumieni wliczone jest:PRETTY_DEFAULT_WDECORATION
.C) Nakładaj dekorację na strumienie
Funkcja
pretty::decoration
służy do nakładania dekoracji na określony strumień. Występują przeciążenia, które przyjmują albo - jeden argument ciągu jest ogranicznikiem (przyjmując prefiks i postfiks z domyślnej klasy) - lub trzy argumenty ciągu składające całą dekoracjęKompletna dekoracja dla danego typu i strumienia
Dostosowanie separatora dla danego strumienia
4. Specjalne postępowanie z
std::tuple
Zamiast zezwalania na specjalizację dla każdego możliwego rodzaju krotki, ten kod stosuje każdą dekorację dostępną dla
std::tuple<void*>
wszystkich rodzajówstd::tuple<...>
.5. Usuń niestandardową dekorację ze strumienia
Aby wrócić do domyślnej dekoracji dla danego typu, użyj
pretty::clear
szablonu funkcji w strumienius
.5. Dalsze przykłady
Drukowanie „podobnego do matrycy” za pomocą separatora nowego wiersza
Wydruki
Zobacz na ideone / KKUebZ
6. Kod
źródło
Dodam tutaj kolejną odpowiedź, ponieważ wpadłem na inne podejście do mojej poprzedniej, a mianowicie wykorzystanie aspektów regionalnych.
Podstawy są tutaj
Zasadniczo to, co robisz, to:
std::locale::facet
. Lekkim minusem jest to, że będziesz potrzebować gdzieś kompilatora do przechowywania jego identyfikatora. Nazwijmy to MyPrettyVectorPrinter. Prawdopodobnie nadałbyś mu lepszą nazwę, a także stworzyłeś dla pary i mapy.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
operator<<
) udostępnia te domyślne. Pamiętaj, że możesz zrobić to samo, czytając wektor.Podoba mi się ta metoda, ponieważ możesz użyć domyślnego wydruku, a jednocześnie możesz użyć niestandardowego zastąpienia.
Wady potrzebują biblioteki dla twojego aspektu, jeśli są używane w wielu projektach (więc nie mogą być tylko nagłówkami), a także faktem, że musisz uważać na koszt utworzenia nowego obiektu ustawień regionalnych.
Napisałem to jako nowe rozwiązanie zamiast modyfikować moje inne, ponieważ uważam, że oba podejścia mogą być poprawne, a ty wybierasz.
źródło
Celem jest tutaj użycie ADL do dostosowania tego, jak ładnie drukujemy.
Przekazujesz tag formatera i zastępujesz 4 funkcje (przed, po, pomiędzy i schodzisz) w przestrzeni nazw znacznika. Zmienia to sposób, w jaki formatyzator drukuje „ozdoby” podczas iteracji nad pojemnikami.
Domyślny formater, który działa
{(a->b),(c->d)}
dla map,(a,b,c)
dla tupleoidów,"hello"
dla ciągów znaków,[x,y,z]
dla wszystkich innych zawartych elementów.Powinien „po prostu działać” z iterowalnymi typami innych firm (i traktować je jak „wszystko inne”).
Jeśli chcesz niestandardowe ozdoby dla iteratorów innych firm, po prostu stwórz własny tag. Zajmie to trochę pracy, aby obsłużyć zejście mapy (musisz
pretty_print_descend( your_tag
wrócić do przeciążeniapretty_print::decorator::map_magic_tag<your_tag>
). Być może istnieje na to czystszy sposób, nie jestem pewien.Mała biblioteka do wykrywania iterowalności i krotności:
Biblioteka, która pozwala nam odwiedzić zawartość iterowalnego lub krotkowego obiektu:
Ładna biblioteka do drukowania:
Kod testowy:
przykład na żywo
To używa funkcji C ++ 14 (niektóre
_t
aliasy iauto&&
lambda), ale żadna nie jest niezbędna.źródło
->
w ciągupair
sekund odmap
S) w tym punkcie. Rdzeń ładnej biblioteki drukowania jest ładny i mały, co jest miłe. Starałem się, aby był łatwo rozszerzalny, nie jestem pewien, czy mi się udało.Moje rozwiązanie jest proste. H , które jest częścią pakietu scc . Wszystkie pojemniki standardowe, mapy, zestawy, tablice c można drukować.
źródło
i
?std::set
niestandardowego komparatora lub nieuporządkowanej_mapy o niestandardowej równości. Wspieranie tych konstrukcji byłoby bardzo ważne.Wychodząc z jednego z pierwszych BoostConów (obecnie nazywanych CppCon), ja i dwoje innych pracowaliśmy nad biblioteką, aby to zrobić. Głównym punktem spornym było rozszerzenie standardowej przestrzeni nazw. Okazało się, że nie można iść na bibliotekę doładowań.
Niestety linki do kodu już nie działają, ale w dyskusjach możesz znaleźć ciekawe ciekawostki (przynajmniej te, które nie mówią o tym, jak go nazwać!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
źródło
Oto moja wersja wdrożenia wykonana w 2016 roku
Wszystko w jednym nagłówku, więc jest łatwy w użyciu https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
źródło