Veuillez prendre note des mises à jour à la fin de cet article.
Mise à jour: j'ai créé un projet public sur GitHub pour cette bibliothèque!
J'aimerais avoir un modèle unique qui, une fois pour toutes, s'occupe de l'impression de tous les conteneurs STL via operator<<
. En pseudo code, je cherche quelque chose comme ça:
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;
}
Maintenant, j'ai vu beaucoup de magie de modèle ici sur SO que je n'aurais jamais pensé possible, donc je me demande si quelqu'un peut suggérer quelque chose qui correspondrait à tous les conteneurs C. Peut-être quelque chose de trait qui peut déterminer si quelque chose a l'itérateur nécessaire ?
Merci beaucoup!
Mise à jour (et solution)
Après avoir à nouveau soulevé ce problème sur le canal 9 , j'ai obtenu une réponse fantastique de Sven Groot, qui, combiné avec un peu de trait de type SFINAE, semble résoudre le problème d'une manière complètement générale et emboîtable. Les délimiteurs peuvent être spécialisés individuellement, un exemple de spécialisation pour std :: set est inclus, ainsi qu'un exemple d'utilisation de délimiteurs personnalisés.
L'assistant "wrap_array ()" peut être utilisé pour imprimer des tableaux C bruts. Mise à jour: les paires et les tuples sont disponibles pour l'impression; les délimiteurs par défaut sont des crochets ronds.
Le trait de type enable-if nécessite C ++ 0x, mais avec quelques modifications, il devrait être possible d'en faire une version C ++ 98. Les tuples nécessitent des modèles variadiques, donc C ++ 0x.
J'ai demandé à Sven de publier la solution ici afin de pouvoir l'accepter, mais en attendant, j'aimerais publier le code moi-même pour référence. ( Mise à jour: Sven a maintenant publié son code ci-dessous, dont j'ai fait la réponse acceptée. Mon propre code utilise des traits de type de conteneur, qui fonctionnent pour moi mais peuvent provoquer un comportement inattendu avec les classes non-conteneur qui fournissent des itérateurs.)
En-tête (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
Exemple d'utilisation:
#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
;
}
Autres idées d'améliorations:
Implémenter la sortie pourMise à jour: Ceci est maintenant une question distincte sur SO ! Mise à jour: Ceci est maintenant implémenté, grâce à Xeo!std::tuple<...>
de la même manière que nous l'avons pourstd::pair<S,T>
.Ajoutez des espaces de noms afin que les classes auxiliaires ne saignent pas dans l'espace de noms global.Terminé- Ajouter des alias de modèle (ou quelque chose de similaire) pour faciliter la création de classes de délimiteur personnalisées, ou peut-être des macros de préprocesseur?
Mises à jour récentes:
- J'ai supprimé l'itérateur de sortie personnalisé au profit d'une boucle for simple dans la fonction d'impression.
- Tous les détails d'implémentation sont maintenant dans l'
pretty_print
espace de noms. Seuls les opérateurs de flux global et l'pretty_print_array
encapsuleur se trouvent dans l'espace de noms global. - L'espace de noms a été corrigé afin qu'il
operator<<
soit désormais correctement intégréstd
.
Remarques:
- La suppression de l'itérateur de sortie signifie qu'il n'y a aucun moyen d'utiliser
std::copy()
pour obtenir une jolie impression. Je pourrais rétablir le joli itérateur si c'est une fonctionnalité souhaitée, mais le code de Sven ci-dessous a l'implémentation. - C'était une décision de conception consciente de faire des délimiteurs des constantes de temps de compilation plutôt que des constantes d'objet. Cela signifie que vous ne pouvez pas fournir de délimiteurs de manière dynamique lors de l'exécution, mais cela signifie également qu'il n'y a pas de surcharge inutile. Une configuration de délimiteur basée sur les objets a été proposée par Dennis Zickefoose dans un commentaire sur le code de Sven ci-dessous. Si vous le souhaitez, cela pourrait être implémenté comme une fonctionnalité alternative.
- Il n'est actuellement pas évident de personnaliser les délimiteurs de conteneurs imbriqués.
- Gardez à l'esprit que le but de cette bibliothèque est de permettre des installations d'impression de conteneurs rapides qui ne nécessitent aucun codage de votre part. Ce n'est pas une bibliothèque de formatage polyvalente, mais plutôt un outil de développement pour réduire le besoin d'écrire du code de plaque de chaudière pour l'inspection des conteneurs.
Merci à tous ceux qui ont contribué!
Remarque: Si vous recherchez un moyen rapide de déployer des délimiteurs personnalisés, voici une façon d'utiliser l'effacement de type. Nous supposons que vous avez déjà construit une classe de délimitation, disons MyDel
comme ceci:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Maintenant, nous voulons pouvoir écrire std::cout << MyPrinter(v) << std::endl;
pour un conteneur en v
utilisant ces délimiteurs. MyPrinter
sera une classe d'effacement de type, comme ceci:
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); }
la source
pretty_print
espace de noms et de fournir un wrapper à utiliser par l'utilisateur lors de l'impression. Du point de vue de l'utilisateur:std::cout << pretty_print(v);
(probablement avec un nom différent). Ensuite, vous pouvez fournir l'opérateur dans le même espace de noms que le wrapper, et il peut ensuite s'étendre pour imprimer tout ce que vous voulez. Vous pouvez également améliorer l'encapsuleur en permettant éventuellement de définir le séparateur à utiliser dans chaque appel (plutôt que d'utiliser des traits qui forcent le même choix pour l'ensemble de l'application) \Réponses:
Cette solution a été inspirée de la solution de Marcelo, avec quelques changements:
Comme la version de Marcelo, il utilise un trait de type is_container qui doit être spécialisé pour tous les conteneurs qui doivent être pris en charge. Il peut être possible d'utiliser un trait pour vérifier
value_type
,const_iterator
,begin()
/end()
, mais je ne suis pas certaine que je recommande car il pourrait correspondre à des choses qui correspondent à ces critères , mais ne sont pas réellement des conteneurs, commestd::basic_string
. Comme la version de Marcelo, il utilise des modèles qui peuvent être spécialisés pour spécifier les délimiteurs à utiliser.La principale différence est que j'ai construit ma version autour de a
pretty_ostream_iterator
, qui fonctionne de manière similaire à lastd::ostream_iterator
mais qui n'imprime pas de délimiteur après le dernier élément. Le formatage des conteneurs est effectué par leprint_container_helper
, qui peut être utilisé directement pour imprimer des conteneurs sans trait is_container, ou pour spécifier un type de délimiteurs différent.J'ai également défini is_container et délimiteurs afin qu'il fonctionne pour les conteneurs avec des prédicats ou des allocateurs non standard, et pour char et wchar_t. La fonction operator << elle-même est également définie pour fonctionner avec les flux char et wchar_t.
Enfin, j'ai utilisé
std::enable_if
, qui est disponible dans le cadre de C ++ 0x, et fonctionne dans Visual C ++ 2010 et g ++ 4.3 (nécessite l'indicateur -std = c ++ 0x) et versions ultérieures. De cette façon, il n'y a aucune dépendance à Boost.la source
<i, j>
dans une fonction et comme[i j]
dans une autre, vous devez définir un tout nouveau type, avec une poignée de membres statiques afin de passer ce type àprint_container_helper
? Cela semble trop complexe. Pourquoi ne pas aller avec un objet réel, avec des champs que vous pouvez définir au cas par cas, et les spécialisations fournissant simplement des valeurs par défaut différentes?print_container_helper
n'est pas aussi élégante que leoperator<<
. Vous pouvez toujours changer la source, bien sûr, ou simplement ajouter des spécialisations explicites pour votre conteneur préféré, par exemple pourpair<int, int>
et pourpair<double, string>
. En fin de compte, il s'agit de comparer le pouvoir à la commodité. Suggestions d'amélioration bienvenue!MyDels
, je peux le direstd::cout << CustomPrinter<MyDels>(x);
. Ce que je ne peux pas faire pour le moment, c'est direstd::cout << CustomDelims<"{", ":", "}">(x);
, parce que vous ne pouvez pas avoir d'const char *
arguments de modèle. La décision de faire des délimiteurs une constante de temps de compilation impose des restrictions sur la facilité d'utilisation, mais je pense que cela en vaut la peine.Cela a été édité plusieurs fois, et nous avons décidé d'appeler la classe principale qui encapsule une collection RangePrinter
Cela devrait fonctionner automatiquement avec n'importe quelle collection une fois que vous avez écrit l'opérateur en une seule fois << surcharge, sauf que vous en aurez besoin d'une spéciale pour les cartes pour imprimer la paire et que vous souhaiterez peut-être personnaliser le délimiteur à cet endroit.
Vous pouvez également avoir une fonction spéciale "imprimer" à utiliser sur l'élément au lieu de simplement le sortir directement. Un peu comme les algorithmes STL vous permettent de passer des prédicats personnalisés. Avec map, vous l'utiliseriez de cette façon, avec une imprimante personnalisée pour la paire std ::.
Votre imprimante "par défaut" la sortira simplement dans le flux.
Ok, travaillons sur une imprimante personnalisée. Je changerai ma classe externe en RangePrinter. Nous avons donc 2 itérateurs et certains délimiteurs, mais nous n'avons pas personnalisé la façon d'imprimer les éléments réels.
Maintenant, par défaut, cela fonctionnera pour les cartes tant que les types de clé et de valeur sont tous les deux imprimables et vous pouvez mettre dans votre propre imprimante d'élément spécial pour quand ils ne le sont pas (comme vous pouvez avec n'importe quel autre type), ou si vous ne voulez pas = comme délimiteur.
Je déplace la fonction gratuite pour les créer à la fin maintenant:
Une fonction libre (version itérateur) ressemblerait à quelque chose comme ça et vous pourriez même avoir des valeurs par défaut:
Vous pouvez ensuite l'utiliser pour std :: set by
Vous pouvez également écrire une version à fonction libre qui prend une imprimante personnalisée et celles qui prennent deux itérateurs. Dans tous les cas, ils résoudront les paramètres du modèle pour vous et vous pourrez les passer en tant que temporaires.
la source
std::cout << outputFormatter(beginOfRange, endOfRange);
.std::pair
est l'exemple le plus élémentaire de "collection intérieure".std::map
s facilement et s'il fonctionne pour les collections de collections? Je suis cependant tenté d'accepter celle-ci comme réponse. J'espère que cela ne dérange pas Marcelo, sa solution fonctionne également.Voici une bibliothèque de travail, présentée comme un programme de travail complet, que je viens de pirater ensemble:
Il ne fonctionne actuellement qu'avec
vector
etset
, mais peut fonctionner avec la plupart des conteneurs, simplement en développant lesIsContainer
spécialisations. Je n'ai pas beaucoup réfléchi à la question de savoir si ce code est minimal, mais je ne peux pas immédiatement penser à quoi que ce soit que je pourrais éliminer comme redondant.EDIT: Juste pour les coups de pied, j'ai inclus une version qui gère les tableaux. J'ai dû exclure les tableaux de caractères pour éviter de nouvelles ambiguïtés; il pourrait encore avoir des ennuis avec
wchar_t[]
.la source
std::map<>
en charge soit en spécialisant l'opérateur, soit en définissant unoperator<<
forstd::pair<>
.Delims
modèle de classe!operator<<
modèle correspond à peu près à tout.Vous pouvez formater des conteneurs ainsi que des plages et des tuples à l' aide de la bibliothèque {fmt} . Par exemple:
impressions
à
stdout
.Avertissement : je suis l'auteur de {fmt}.
la source
Le code s'est avéré pratique à plusieurs reprises maintenant et je ressens le coût de la personnalisation car l'utilisation est assez faible. J'ai donc décidé de le publier sous licence MIT et de fournir un référentiel GitHub où l'en-tête et un petit exemple de fichier peuvent être téléchargés.
http://djmuw.github.io/prettycc
0. Préface et libellé
Une «décoration» en termes de cette réponse est un ensemble de chaînes de préfixe, de chaîne de délimitation et de chaîne de suffixe. Où la chaîne de préfixe est insérée dans un flux avant et la chaîne de suffixe après les valeurs d'un conteneur (voir 2. Conteneurs cibles). La chaîne de délimitation est insérée entre les valeurs du conteneur respectif.
Remarque: En fait, cette réponse n'adresse pas la question à 100% car la décoration n'est pas strictement constante dans le temps car des vérifications d'exécution sont requises pour vérifier si une décoration personnalisée a été appliquée au flux actuel. Néanmoins, je pense qu'il a des caractéristiques décentes.
Note2: Peut avoir des bugs mineurs car il n'est pas encore bien testé.
1. Idée générale / utilisation
Aucun code supplémentaire requis pour l'utilisation
Il doit être aussi simple que possible
Personnalisation facile ...
... par rapport à un objet de flux spécifique
ou en ce qui concerne tous les flux:
Description approximative
ios_base
utilisantxalloc
/pword
afin d'enregistrer un pointeur sur unpretty::decor
objet décorant spécifiquement un certain type sur un certain flux.Si aucun
pretty::decor<T>
objet pour ce flux n'a été configuré explicitement, ilpretty::defaulted<T, charT, chartraitT>::decoration()
est appelé pour obtenir la décoration par défaut pour le type donné. La classepretty::defaulted
doit être spécialisée pour personnaliser les décorations par défaut.2. Objets / conteneurs cibles
Les objets cibles
obj
pour la «jolie décoration» de ce code sont des objets ayant soitstd::begin
etstd::end
définies (comprend les tableaux de style C),begin(obj)
etend(obj)
disponible via ADL,std::tuple
std::pair
.Le code comprend un trait pour l'identification des classes avec des caractéristiques de plage (
begin
/end
). (Cependant, il n'y a pas de vérification, s'ilbegin(obj) == end(obj)
s'agit d'une expression valide.)Le code fournit des
operator<<
s dans l'espace de noms global qui ne s'appliquent qu'aux classes n'ayant pas de version plus spécialisée deoperator<<
disponible. Par conséquent, par exemple,std::string
n'est pas imprimé à l'aide de l'opérateur dans ce code bien qu'il ait une pairebegin
/ valideend
.3. Utilisation et personnalisation
Les décorations peuvent être imposées séparément pour chaque type (sauf différents
tuple
s) et flux (pas de type flux!). (C'est-à-dire qu'unstd::vector<int>
peut avoir différentes décorations pour différents objets de flux.)A) Décoration par défaut
Le préfixe par défaut est
""
(rien), tout comme le suffixe par défaut, tandis que le délimiteur par défaut est", "
(virgule + espace).B) Décoration par défaut personnalisée d'un type en spécialisant le
pretty::defaulted
modèle de classeLe
struct defaulted
possède une fonction membre statiquedecoration()
renvoyant undecor
objet qui inclut les valeurs par défaut pour le type donné.Exemple utilisant un tableau:
Personnalisez l'impression par défaut du réseau:
Imprimer un tableau arry:
Utilisation de la
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
macro pour leschar
fluxLa macro se développe pour
permettant de réécrire la spécialisation partielle ci-dessus
ou en insérant une spécialisation complète comme
Une autre macro pour les
wchar_t
flux est inclus:PRETTY_DEFAULT_WDECORATION
.C) Imposer une décoration sur les ruisseaux
La fonction
pretty::decoration
est utilisée pour imposer une décoration sur un certain flux. Il y a des surcharges prenant - un argument de chaîne étant le délimiteur (en adoptant le préfixe et le suffixe de la classe par défaut) - ou trois arguments de chaîne assemblant la décoration complèteDécoration complète pour le type et le flux donnés
Personnalisation du délimiteur pour un flux donné
4. Traitement spécial des
std::tuple
Au lieu de permettre une spécialisation pour chaque type de tuple possible, ce code applique toute décoration disponible pour
std::tuple<void*>
tous les types destd::tuple<...>
s.5. Supprimer la décoration personnalisée du flux
Pour revenir à la décoration par défaut pour un type donné, utilisez le
pretty::clear
modèle de fonction sur le fluxs
.5. Autres exemples
Impression "matricielle" avec délimiteur de nouvelle ligne
Impressions
Voir sur ideone / KKUebZ
6. Code
la source
Je vais ajouter une autre réponse ici, car j'ai trouvé une approche différente de la précédente, à savoir utiliser des facettes locales.
Les bases sont ici
Ce que vous faites, c'est essentiellement:
std::locale::facet
. Le léger inconvénient est que vous aurez besoin d'une unité de compilation quelque part pour conserver son identifiant. Appelons cela MyPrettyVectorPrinter. Vous lui donneriez probablement un meilleur nom et en créeriez également pour la paire et la carte.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
operator<<
) en fournit des par défaut. Notez que vous pouvez faire la même chose pour lire un vecteur.J'aime cette méthode car vous pouvez utiliser une impression par défaut tout en étant capable d'utiliser un remplacement personnalisé.
Les inconvénients nécessitent une bibliothèque pour votre facette si elle est utilisée dans plusieurs projets (il ne peut donc pas s'agir uniquement d'en-têtes) et du fait que vous devez vous méfier des frais de création d'un nouvel objet de paramètres régionaux.
J'ai écrit cela comme une nouvelle solution plutôt que de modifier mon autre parce que je crois que les deux approches peuvent être correctes et vous faites votre choix.
la source
Le but ici est d'utiliser ADL pour personnaliser la façon dont nous imprimons.
Vous passez une balise de formateur et remplacez 4 fonctions (avant, après, entre et descendre) dans l'espace de noms de la balise. Cela change la façon dont le formateur imprime les «ornements» lors de l'itération sur les conteneurs.
Un formateur par défaut qui fait
{(a->b),(c->d)}
pour les cartes,(a,b,c)
pour les tupleoids,"hello"
pour les chaînes,[x,y,z]
pour tout le reste inclus.Il devrait "simplement fonctionner" avec des types itérables tiers (et les traiter comme "tout le reste").
Si vous voulez des ornements personnalisés pour vos itérables tiers, créez simplement votre propre étiquette. Il faudra un peu de travail pour gérer la descente de la carte (vous devez surcharger
pretty_print_descend( your_tag
pour revenirpretty_print::decorator::map_magic_tag<your_tag>
). Peut-être existe-t-il une façon plus propre de procéder, je n'en suis pas sûr.Une petite bibliothèque pour détecter l'itérabilité et le tuple-ness:
Une bibliothèque qui nous permet de visiter le contenu d'un objet de type itérable ou tuple:
Une jolie bibliothèque d'impression:
Code de test:
exemple en direct
Cela utilise des fonctionnalités C ++ 14 (certains
_t
alias etauto&&
lambdas), mais aucune n'est essentielle.la source
->
dans lepair
s demap
s) à ce stade. Le noyau de la jolie bibliothèque d'impression est agréable et petit, ce qui est agréable. J'ai essayé de le rendre facilement extensible, je ne sais pas si j'ai réussi.Ma solution est simple.h , qui fait partie du paquet scc . Tous les conteneurs std, cartes, ensembles, c-tableaux sont imprimables.
la source
i
?std::set
avec un comparateur personnalisé, ou un unordered_map avec une égalité personnalisée. Il serait très important de soutenir ces constructions.Sortant de l'un des premiers BoostCon (maintenant appelé CppCon), moi et deux autres avons travaillé sur une bibliothèque pour faire exactement cela. Le principal point d'achoppement devait étendre l'espace de noms std. Cela s'est avéré être un no-go pour une bibliothèque de boost.
Malheureusement, les liens vers le code ne fonctionnent plus, mais vous pourriez trouver des informations intéressantes dans les discussions (du moins celles qui ne disent pas comment le nommer!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
la source
Voici ma version d'implémentation réalisée en 2016
Tout dans un en-tête, il est donc facile à utiliser https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
la source