Bitte beachten Sie die Updates am Ende dieses Beitrags.
Update: Ich habe ein öffentliches Projekt auf GitHub für diese Bibliothek erstellt!
Ich hätte gerne eine einzige Vorlage, die ein für alle Mal dafür sorgt, dass alle STL-Container über hübsch gedruckt werden operator<<
. Im Pseudocode suche ich so etwas:
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;
}
Jetzt habe ich hier auf SO viel Template-Magie gesehen, die ich nie für möglich gehalten hätte, und ich frage mich, ob jemand etwas vorschlagen kann, das zu allen Containern C passt. Vielleicht etwas, das herausfinden kann, ob etwas den erforderlichen Iterator hat ?
Danke vielmals!
Update (und Lösung)
Nachdem ich dieses Problem auf Kanal 9 erneut angesprochen hatte, erhielt ich eine fantastische Antwort von Sven Groot, die zusammen mit ein wenig SFINAE-Typ-Traiting das Problem auf eine völlig allgemeine und verschachtelbare Weise zu lösen scheint. Die Trennzeichen können individuell spezialisiert sein, eine Beispielspezialisierung für std :: set ist enthalten sowie ein Beispiel für die Verwendung benutzerdefinierter Trennzeichen.
Mit dem Helfer "wrap_array ()" können rohe C-Arrays gedruckt werden. Update: Paare und Tupel stehen zum Drucken zur Verfügung. Standardtrennzeichen sind runde Klammern.
Das Merkmal enable-if erfordert C ++ 0x, aber mit einigen Änderungen sollte es möglich sein, eine C ++ 98-Version davon zu erstellen. Tupel erfordern verschiedene Vorlagen, daher C ++ 0x.
Ich habe Sven gebeten, die Lösung hier zu veröffentlichen, damit ich sie akzeptieren kann, aber in der Zwischenzeit möchte ich den Code selbst als Referenz veröffentlichen. ( Update: Sven hat jetzt seinen Code unten veröffentlicht, auf den ich die akzeptierte Antwort gegeben habe. Mein eigener Code verwendet Containertypmerkmale, die für mich funktionieren, aber bei Nicht-Container-Klassen, die Iteratoren bereitstellen, unerwartetes Verhalten verursachen können.)
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
Anwendungsbeispiel:
#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
;
}
Weitere Verbesserungsvorschläge:
Implementieren Sie die Ausgabe fürUpdate: Dies ist jetzt eine separate Frage zu SO ! Upupdate: Dies wurde jetzt dank Xeo implementiert!std::tuple<...>
auf die gleiche Weise, für die wir sie habenstd::pair<S,T>
.Fügen Sie Namespaces hinzu, damit die Hilfsklassen nicht in den globalen Namespace übergehen.Erledigt- Fügen Sie Vorlagen-Aliase (oder ähnliches) hinzu, um das Erstellen benutzerdefinierter Trennzeichenklassen oder möglicherweise Präprozessor-Makros zu erleichtern.
Neueste Updates:
- Ich habe den benutzerdefinierten Ausgabe-Iterator zugunsten einer einfachen for-Schleife in der Druckfunktion entfernt.
- Alle Implementierungsdetails befinden sich jetzt im
pretty_print
Namespace. Nur die globalen Stream-Operatoren und derpretty_print_array
Wrapper befinden sich im globalen Namespace. - Der Namespace wurde so korrigiert, dass er
operator<<
jetzt korrekt angezeigt wirdstd
.
Anmerkungen:
- Das Entfernen des Ausgabe-Iterators bedeutet, dass es keine Möglichkeit gibt
std::copy()
, einen hübschen Druck zu erhalten. Ich könnte den hübschen Iterator wieder einsetzen, wenn dies eine gewünschte Funktion ist, aber der folgende Code von Sven hat die Implementierung. - Es war eine bewusste Entwurfsentscheidung, die Begrenzer eher zur Konstante der Kompilierungszeit als zu Objektkonstanten zu machen. Das bedeutet, dass Sie zur Laufzeit keine Trennzeichen dynamisch bereitstellen können, aber es bedeutet auch, dass kein unnötiger Overhead entsteht. Eine objektbasierte Begrenzerkonfiguration wurde von Dennis Zickefoose in einem Kommentar zu Svens Code unten vorgeschlagen. Falls gewünscht, könnte dies als alternative Funktion implementiert werden.
- Es ist derzeit nicht klar, wie verschachtelte Containerbegrenzer angepasst werden sollen.
- Beachten Sie, dass der Zweck dieser Bibliothek darin besteht, schnelle Containerdruckfunktionen zu ermöglichen , für die Sie keine Codierung benötigen . Es handelt sich nicht um eine Allzweck-Formatierungsbibliothek, sondern um ein Entwicklungswerkzeug, mit dem die Notwendigkeit verringert werden muss, Kesselplattencode für die Behälterinspektion zu schreiben.
Vielen Dank an alle, die dazu beigetragen haben!
Hinweis: Wenn Sie nach einer schnellen Möglichkeit suchen, benutzerdefinierte Trennzeichen bereitzustellen, finden Sie hier eine Möglichkeit zum Löschen von Typen. Wir gehen davon aus, dass Sie bereits eine Trennzeichenklasse erstellt haben MyDel
, beispielsweise wie folgt:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Jetzt möchten wir in der Lage sein, mit diesen Trennzeichen std::cout << MyPrinter(v) << std::endl;
für einen Container zu schreiben v
. MyPrinter
wird eine typlöschende Klasse sein, wie folgt:
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); }
quelle
pretty_print
Namespace einzufügen und einen Wrapper bereitzustellen, den der Benutzer beim Drucken verwenden kann. Aus Anwendersicht:std::cout << pretty_print(v);
(wahrscheinlich mit einem anderen Namen). Dann können Sie den Operator im selben Namespace wie den Wrapper bereitstellen und ihn dann erweitern, um alles zu drucken, was Sie wollen. Sie können auch den Wrapper erweitern, indem Sie optional das Trennzeichen definieren, das in jedem Aufruf verwendet werden soll (anstatt Merkmale zu verwenden, die dieselbe Auswahl für die gesamte Anwendung erzwingen). \Antworten:
Diese Lösung wurde mit einigen Änderungen von Marcelos Lösung inspiriert:
Wie in der Marcelo-Version wird ein Merkmal vom Typ is_container verwendet, das auf alle zu unterstützenden Container spezialisiert sein muss. Es kann möglich sein , ein Merkmal zu verwenden , um zu überprüfen
value_type
,const_iterator
,begin()
/end()
, aber ich bin nicht sicher , würde ich das empfehlen , da es Dinge passen könnte, die diese Kriterien entsprechen , sind aber nicht wirklich Container, wiestd::basic_string
. Ebenso wie in Marcelos Version werden Vorlagen verwendet, die darauf spezialisiert werden können, die zu verwendenden Trennzeichen anzugeben.Der Hauptunterschied besteht darin, dass ich meine Version um eine herum erstellt habe
pretty_ostream_iterator
, die ähnlich wie diestd::ostream_iterator
funktioniert, aber nach dem letzten Element kein Trennzeichen druckt. Das Formatieren der Container erfolgt über dasprint_container_helper
, mit dem Container ohne is_container-Merkmal direkt gedruckt oder ein anderer Trennzeichentyp angegeben werden kann.Ich habe auch is_container und Trennzeichen definiert, damit es für Container mit nicht standardmäßigen Prädikaten oder Allokatoren sowie für char und wchar_t funktioniert. Die Operator << -Funktion selbst ist auch so definiert, dass sie sowohl mit char- als auch mit wchar_t-Streams arbeitet.
Schließlich habe ich verwendet
std::enable_if
, das als Teil von C ++ 0x verfügbar ist und in Visual C ++ 2010 und g ++ 4.3 (benötigt das Flag -std = c ++ 0x) und höher funktioniert. Auf diese Weise besteht keine Abhängigkeit von Boost.quelle
<i, j>
in einer Funktion und wie[i j]
in einer anderen gedruckt wirdprint_container_helper
. Das scheint zu komplex. Warum nicht mit einem tatsächlichen Objekt arbeiten, mit Feldern, die Sie von Fall zu Fall festlegen können, und den Spezialisierungen, die einfach unterschiedliche Standardwerte bereitstellen?print_container_helper
nicht so elegant ist wie dieoperator<<
. Sie können natürlich jederzeit die Quelle ändern oder einfach explizite Spezialisierungen für Ihren Lieblingscontainer hinzufügen, z . B. fürpair<int, int>
und fürpair<double, string>
. Letztendlich geht es darum, Macht gegen Bequemlichkeit abzuwägen. Verbesserungsvorschläge willkommen!MyDels
, kann ich sagenstd::cout << CustomPrinter<MyDels>(x);
. Was ich im Moment nicht tun kann , ist zu sagenstd::cout << CustomDelims<"{", ":", "}">(x);
, weil Sie keineconst char *
Vorlagenargumente haben können . Die Entscheidung, die Kompilierungszeit der Trennzeichen konstant zu halten, schränkt die Benutzerfreundlichkeit dort ein, aber ich denke, es lohnt sich.Dies wurde einige Male bearbeitet, und wir haben beschlossen, die Hauptklasse aufzurufen, die eine Sammlung RangePrinter umschließt
Dies sollte automatisch mit jeder Sammlung funktionieren, sobald Sie den einmaligen Operator << overload geschrieben haben, außer dass Sie einen speziellen Operator für Karten benötigen, um das Paar zu drucken, und möglicherweise das Trennzeichen dort anpassen möchten.
Sie können auch eine spezielle "Druck" -Funktion für das Element verwenden, anstatt es nur direkt auszugeben. Ähnlich wie bei STL-Algorithmen können Sie benutzerdefinierte Prädikate übergeben. Mit map würden Sie es auf diese Weise verwenden, mit einem benutzerdefinierten Drucker für das std :: pair.
Ihr "Standard" -Drucker würde es nur in den Stream ausgeben.
Ok, lassen Sie uns an einem benutzerdefinierten Drucker arbeiten. Ich werde meine äußere Klasse in RangePrinter ändern. Wir haben also 2 Iteratoren und einige Trennzeichen, aber nicht angepasst, wie die tatsächlichen Elemente gedruckt werden sollen.
Jetzt funktioniert es standardmäßig für Karten, solange sowohl der Schlüssel- als auch der Wertetyp druckbar sind und Sie Ihren eigenen Spezialdrucker einsetzen können, wenn dies nicht der Fall ist (wie bei jedem anderen Typ) oder wenn Sie dies nicht möchten = als Trennzeichen.
Ich verschiebe die freie Funktion, um diese jetzt bis zum Ende zu erstellen:
Eine freie Funktion (Iterator-Version) würde so aussehen und Sie könnten sogar Standardeinstellungen haben:
Sie können es dann für std :: set by verwenden
Sie können auch eine Version mit freien Funktionen schreiben, die einen benutzerdefinierten Drucker und zwei Iteratoren benötigt. In jedem Fall lösen sie die Vorlagenparameter für Sie auf und Sie können sie als temporäre Parameter weitergeben.
quelle
std::cout << outputFormatter(beginOfRange, endOfRange);
.std::pair
ist das grundlegendste Beispiel für "innere Sammlung".std::map
leicht mit s umgehen kann und ob es für Sammlungen von Sammlungen funktioniert? Ich bin jedoch versucht, dies als Antwort zu akzeptieren. Ich hoffe, Marcelo macht das nichts aus, seine Lösung funktioniert auch.Hier ist eine Arbeitsbibliothek, die als komplettes Arbeitsprogramm präsentiert wird und die ich gerade zusammen gehackt habe:
Es funktioniert derzeit nur mit
vector
undset
, kann aber für die meisten Container verwendet werden, indem nur dieIsContainer
Spezialisierungen erweitert werden. Ich habe nicht viel darüber nachgedacht, ob dieser Code minimal ist, aber ich kann mir nichts vorstellen, was ich als redundant herausnehmen könnte.EDIT: Nur zum Spaß habe ich eine Version hinzugefügt, die Arrays handhabt. Ich musste char-Arrays ausschließen, um weitere Unklarheiten zu vermeiden. es könnte immer noch in Schwierigkeiten geraten
wchar_t[]
.quelle
std::map<>
entweder durch Spezialisierung des Operators oder durch Definieren einesoperator<<
for unterstützenstd::pair<>
.Delims
Klassenvorlage!operator<<
Vorlage mit fast allem übereinstimmt.Sie können Container sowie Bereiche und Tupel mithilfe der {fmt} -Bibliothek formatieren . Zum Beispiel:
druckt
zu
stdout
.Haftungsausschluss : Ich bin der Autor von {fmt}.
quelle
Der Code hat sich bereits mehrmals als nützlich erwiesen, und ich bin der Meinung, dass die Kosten für die Anpassung sehr gering sind, da die Nutzung recht gering ist. Daher habe ich beschlossen, es unter MIT- Lizenz freizugeben und ein GitHub-Repository bereitzustellen, in das der Header und eine kleine Beispieldatei heruntergeladen werden können.
http://djmuw.github.io/prettycc
0. Vorwort und Wortlaut
Eine 'Dekoration' in Bezug auf diese Antwort ist eine Reihe von Präfix-Strings, Trennzeichen-Strings und Postfix-Strings. Wo die Präfixzeichenfolge vor und die Postfixzeichenfolge nach den Werten eines Containers in einen Stream eingefügt wird (siehe 2. Zielcontainer). Die Trennzeichenfolge wird zwischen den Werten des jeweiligen Containers eingefügt.
Hinweis: Tatsächlich beantwortet diese Antwort die Frage nicht zu 100%, da die Dekoration nicht streng kompiliert ist, da Laufzeitprüfungen erforderlich sind, um zu überprüfen, ob eine benutzerdefinierte Dekoration auf den aktuellen Stream angewendet wurde. Trotzdem denke ich, dass es einige anständige Eigenschaften hat.
Hinweis 2: Kann kleinere Fehler aufweisen, da es noch nicht gut getestet wurde.
1. Allgemeine Idee / Verwendung
Für die Verwendung ist kein zusätzlicher Code erforderlich
Es ist so einfach zu halten wie
Einfache Anpassung ...
... in Bezug auf ein bestimmtes Stream-Objekt
oder in Bezug auf alle Streams:
Grobe Beschreibung
ios_base
mitxalloc
/ bereitgestellt wirdpword
, um einen Zeiger auf einpretty::decor
Objekt zu speichern, das speziell einen bestimmten Typ in einem bestimmten Stream dekoriert.Wenn kein
pretty::decor<T>
Objekt für diesen Stream explizit eingerichtet wurde,pretty::defaulted<T, charT, chartraitT>::decoration()
wird aufgerufen, um die Standarddekoration für den angegebenen Typ zu erhalten. Die Klassepretty::defaulted
soll darauf spezialisiert sein, Standarddekorationen anzupassen.2. Ziele Objekte / Container
Zielobjekte
obj
für die 'hübsche Dekoration' dieses Codes sind Objekte mit beidemstd::begin
undstd::end
definiert (einschließlich C-Style-Arrays),begin(obj)
undend(obj)
über ADL verfügbar,std::tuple
std::pair
.Der Code enthält ein Merkmal zur Identifizierung von Klassen mit Bereichsmerkmalen (
begin
/end
). (Es ist jedoch keine Prüfung enthalten, obbegin(obj) == end(obj)
es sich um einen gültigen Ausdruck handelt.)Der Code enthält
operator<<
s im globalen Namespace, die nur für Klassen gelten, für die keine speziellere Versionoperator<<
verfügbar ist. Daher wird beispielsweisestd::string
nicht mit dem Operator in diesem Code gedruckt, obwohl ein gültigesbegin
/end
Paar vorhanden ist.3. Nutzung und Anpassung
Dekorationen können für jeden Typ (außer für verschiedene
tuple
s) und jeden Stream (nicht für jeden Stream-Typ!) Separat auferlegt werden . (Dh astd::vector<int>
kann unterschiedliche Dekorationen für unterschiedliche Stream-Objekte haben.)A) Standarddekoration
Das Standardpräfix ist
""
(nichts), ebenso wie das Standardpostfix, während das Standardtrennzeichen", "
(Komma + Leerzeichen) ist.B) Benutzerdefinierte Standarddekoration eines Typs durch Spezialisierung der
pretty::defaulted
KlassenvorlageDas
struct defaulted
hat eine statischedecoration()
Elementfunktion, die eindecor
Objekt zurückgibt, das die Standardwerte für den angegebenen Typ enthält.Beispiel mit einem Array:
Anpassen des Standard-Array-Drucks:
Drucken Sie ein Arry-Array:
Verwenden des
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
Makros fürchar
StreamsDas Makro wird auf erweitert
Ermöglichen, dass die obige Teilspezialisierung umgeschrieben wird
oder Einfügen einer vollständigen Spezialisierung wie
Ein weiteres Makro für
wchar_t
Streams ist enthalten :PRETTY_DEFAULT_WDECORATION
.C) Bäche dekorieren
Die Funktion
pretty::decoration
wird verwendet, um einem bestimmten Stream eine Dekoration aufzuerlegen. Es gibt Überladungen, die entweder - ein Zeichenfolgenargument ist das Trennzeichen (das Präfix und Postfix aus der Standardklasse übernimmt) - oder drei Zeichenfolgenargumente, die die vollständige Dekoration zusammensetzenKomplette Dekoration für den gegebenen Typ und Strom
Anpassung des Trennzeichens für den angegebenen Stream
4. Besondere Behandlung von
std::tuple
Anstatt eine Spezialisierung für jeden möglichen Tupeltyp zuzulassen, wendet dieser Code jede Dekoration an, die für
std::tuple<void*>
alle Arten vonstd::tuple<...>
s verfügbar ist .5. Entfernen Sie die benutzerdefinierte Dekoration aus dem Stream
Verwenden Sie die
pretty::clear
Funktionsvorlage im Stream, um zur Standarddekoration für einen bestimmten Typ zurückzukehrens
.5. Weitere Beispiele
Drucken "matrixartig" mit Zeilenumbruch
Druckt
Sehen Sie es auf ideone / KKUebZ
6. Code
quelle
Ich werde hier eine weitere Antwort hinzufügen, da ich einen anderen Ansatz als meinen vorherigen entwickelt habe, nämlich die Verwendung von Gebietsschema-Facetten.
Die Grundlagen sind hier
Im Wesentlichen tun Sie Folgendes:
std::locale::facet
. Der kleine Nachteil ist, dass Sie irgendwo eine Kompilierungseinheit benötigen, um ihre ID zu speichern. Nennen wir es MyPrettyVectorPrinter. Sie würden ihm wahrscheinlich einen besseren Namen geben und auch einen für Paar und Karte erstellen.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
operator<<
bietet Ihre Druckfunktion ( ) Standardfunktionen. Beachten Sie, dass Sie beim Lesen eines Vektors dasselbe tun können.Ich mag diese Methode, weil Sie einen Standarddruck verwenden können, während Sie dennoch eine benutzerdefinierte Überschreibung verwenden können.
Die Nachteile sind eine Bibliothek für Ihre Facette, wenn sie in mehreren Projekten verwendet wird (also nicht nur Header) und die Tatsache, dass Sie sich vor den Kosten für die Erstellung eines neuen Gebietsschemaobjekts hüten müssen.
Ich habe dies als neue Lösung geschrieben, anstatt meine andere zu modifizieren, da ich glaube, dass beide Ansätze korrekt sein können und Sie Ihre Wahl treffen.
quelle
Das Ziel hier ist es, ADL zu verwenden, um die Art und Weise anzupassen, wie wir hübsch drucken.
Sie übergeben ein Formatierungs-Tag und überschreiben 4 Funktionen (vor, nach, zwischen und absteigend) im Namespace des Tags. Dies ändert, wie der Formatierer beim Durchlaufen von Containern "Verzierungen" druckt.
Ein Standardformatierer
{(a->b),(c->d)}
für Karten,(a,b,c)
Tupleoide und"hello"
Zeichenfolgen.[x,y,z]
alles andere.Es sollte "nur mit iterierbaren Typen von Drittanbietern funktionieren" (und sie wie "alles andere" behandeln).
Wenn Sie benutzerdefinierte Verzierungen für Ihre Iterables von Drittanbietern wünschen, erstellen Sie einfach Ihr eigenes Tag. Es wird ein wenig Arbeit erfordern, um den Kartenabstieg zu handhaben (Sie müssen überladen,
pretty_print_descend( your_tag
um zurückzukehrenpretty_print::decorator::map_magic_tag<your_tag>
). Vielleicht gibt es einen saubereren Weg, nicht sicher.Eine kleine Bibliothek zum Erkennen von Iterierbarkeit und Tupelhaftigkeit:
Eine Bibliothek, mit der wir den Inhalt eines iterierbaren Objekts oder eines Objekts vom Typ Tupel besuchen können:
Eine hübsche Druckbibliothek:
Testcode:
Live-Beispiel
Dies verwendet zwar C ++ 14-Funktionen (einige
_t
Aliase undauto&&
Lambdas), aber keine sind wesentlich.quelle
->
derpair
s vonmap
s) an dieser Stelle. Der Kern der hübschen Druckbibliothek ist schön klein, was sehr schön ist. Ich habe versucht, es leicht erweiterbar zu machen, nicht sicher, ob es mir gelungen ist.Meine Lösung ist simple.h , die Teil des scc- Pakets ist. Alle Standardcontainer, Karten, Sets und C-Arrays können gedruckt werden.
quelle
i
?std::set
mit einem benutzerdefinierten Komparator oder eine ungeordnete Karte mit einer benutzerdefinierten Gleichheit. Es wäre sehr wichtig, diese Konstruktionen zu unterstützen.Als ich aus einer der ersten BoostCon (jetzt CppCon) herauskam, arbeiteten ich und zwei andere an einer Bibliothek, um genau dies zu tun. Der Hauptknackpunkt war die Erweiterung des Namespace std. Das stellte sich als No-Go für eine Boost-Bibliothek heraus.
Leider funktionieren die Links zum Code nicht mehr, aber in den Diskussionen finden Sie möglicherweise einige interessante Details (zumindest diejenigen, die nicht darüber sprechen, wie sie heißen sollen!).
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
quelle
Hier ist meine Version der Implementierung aus dem Jahr 2016
Alles in einem Header, daher ist es einfach, https://github.com/skident/eos/blob/master/include/eos/io/print.hpp zu verwenden
quelle