librerias - Contenedores C++ STL de impresión bonita
vector stl c++ ejemplos (7)
Por favor tome nota de las actualizaciones al final de esta publicación.
Actualización: ¡He creado un proyecto público en GitHub para esta biblioteca!
Me gustaría tener una única plantilla que, de una vez por todas, se encargue de la impresión de todos los contenedores STL a través del operator<<
. En pseudo código, estoy buscando algo como esto:
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;
}
Ahora he visto un montón de magia de plantillas aquí, de modo que nunca pensé que fuera posible, así que me pregunto si alguien puede sugerir algo que coincida con todos los contenedores C. Tal vez algo que pueda descubrir si algo tiene el iterador necesario ?
¡Muchas gracias!
Actualización (y solución)
Después de volver a plantear este problema en el Canal 9 , recibí una respuesta fantástica de Sven Groot, que, combinada con un poco del tipo de rasgos SFINAE, parece resolver el problema de una manera completamente general y encajable. Los delimitadores pueden especializarse individualmente, se incluye un ejemplo de especialización para std :: set, así como un ejemplo de uso de delimitadores personalizados.
El ayudante "wrap_array ()" se puede utilizar para imprimir matrices C en bruto. Actualización: los pares y las tuplas están disponibles para imprimir; Los delimitadores predeterminados son paréntesis redondos.
El rasgo de tipo enable-if requiere C ++ 0x, pero con algunas modificaciones debería ser posible hacer una versión de C ++ 98 de esto. Las tuplas requieren plantillas variadas, por lo tanto C ++ 0x.
Le he pedido a Sven que publique la solución aquí para poder aceptarla, pero mientras tanto me gustaría publicar el código para referencia. ( Actualización: Sven ahora ha publicado su código a continuación, que hice la respuesta aceptada. Mi propio código utiliza rasgos de tipo contenedor, que funcionan para mí pero pueden causar un comportamiento inesperado con clases que no son contenedores que proporcionan iteradores).
Encabezado (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
Ejemplo de uso:
#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
;
}
Más ideas para mejoras:
-
Implemente la salida paraActualización: ¡ Esta es ahora una pregunta separada sobre SO ! Upupdate: ¡ Esto ya se ha implementado, gracias a Xeo!std::tuple<...>
de la misma manera que lo tenemos parastd::pair<S,T>
. -
Agregue espacios de nombres para que las clases auxiliares no sangren en el espacio de nombres global.Hecho - ¿Agregar alias de plantilla (o algo similar) para facilitar la creación de clases delimitadoras personalizadas, o quizás macros de preprocesador?
Actualizaciones recientes:
- Eliminé el iterador de salida personalizado a favor de un simple bucle for en la función de impresión.
- Todos los detalles de la implementación están ahora en el espacio de nombres de
pretty_print
. Solo los operadores de flujo global y la envolturapretty_print_array
están en el espacio de nombres global. - Se corrigió el espacio de nombre para que el
operator<<
ahora esté correctamente enstd
.
Notas:
- Eliminar el iterador de salida significa que no hay forma de usar
std::copy()
para obtener una impresión bonita. Podría restablecer el bonito iterador si esta es una característica deseada, pero el código de Sven a continuación tiene la implementación. - Fue una decisión de diseño consciente hacer que los delimitadores sean constantes en tiempo de compilación en lugar de constantes de objeto. Eso significa que no puede suministrar delimitadores dinámicamente en tiempo de ejecución, pero también significa que no hay gastos generales innecesarios. Dennis Zickefoose ha propuesto una configuración de delimitador basada en objetos en un comentario al código de Sven a continuación. Si lo desea, esto podría implementarse como una característica alternativa.
- Actualmente no es obvio cómo personalizar los delimitadores de contenedores anidados.
- Tenga en cuenta que el propósito de esta biblioteca es permitir instalaciones de impresión de contenedores rápidos que requieran una codificación cero de su parte. No es una biblioteca de formateo de uso múltiple, sino una herramienta de desarrollo para aliviar la necesidad de escribir el código de la placa de la caldera para la inspección de contenedores.
¡Gracias a todos los que contribuyeron!
Nota: Si está buscando una forma rápida de implementar delimitadores personalizados, aquí hay una forma de utilizar el borrado de tipo. Suponemos que ya ha construido una clase de delimitador, digamos MyDel
, así:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Ahora queremos poder escribir std::cout << MyPrinter(v) << std::endl;
para algún contenedor v
utilizando esos delimitadores. MyPrinter
será una clase de borrado de tipo, así:
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); }
Esta solución se inspiró en la solución de Marcelo, con algunos cambios:
#include <iostream>
#include <iterator>
#include <type_traits>
#include <vector>
#include <algorithm>
// This works similar to ostream_iterator, but doesn''t print a delimiter after the final item
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> >
class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
public:
typedef TChar char_type;
typedef TCharTraits traits_type;
typedef std::basic_ostream<TChar, TCharTraits> ostream_type;
pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL)
: _stream(&stream), _delim(delim), _insertDelim(false)
{
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator=(const T &value)
{
if( _delim != NULL )
{
// Don''t insert a delimiter if this is the first time the function is called
if( _insertDelim )
(*_stream) << _delim;
else
_insertDelim = true;
}
(*_stream) << value;
return *this;
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator*()
{
return *this;
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator++()
{
return *this;
}
pretty_ostream_iterator<T, TChar, TCharTraits>& operator++(int)
{
return *this;
}
private:
ostream_type *_stream;
const char_type *_delim;
bool _insertDelim;
};
#if _MSC_VER >= 1400
// Declare pretty_ostream_iterator as checked
template<typename T, typename TChar, typename TCharTraits>
struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : public std::tr1::true_type
{
};
#endif // _MSC_VER >= 1400
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.
// These aren''t necessary if you do actually include the headers.
template<typename T, typename TAllocator> class vector;
template<typename T, typename TAllocator> class list;
template<typename T, typename TTraits, typename TAllocator> class set;
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> class map;
}
// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public std::false_type { };
// Mark vector as a container
template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { };
// Mark list as a container
template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { };
// Mark set as a container
template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { };
// Mark map as a container
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { };
// 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
{
static const delimiters_values<TChar> 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
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)
{
}
void operator()(ostream_type &stream) const
{
if( delimiters_type::values.prefix != NULL )
stream << delimiters_type::values.prefix;
std::copy(_container->begin(), _container->end(), pretty_ostream_iterator<typename T::value_type, TChar, TCharTraits>(stream, delimiters_type::values.delimiter));
if( delimiters_type::values.postfix != NULL )
stream << delimiters_type::values.postfix;
}
private:
const T *_container;
};
// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper)
{
helper(stream);
return stream;
}
// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type
operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container)
{
stream << print_container_helper<T, TChar, TCharTraits>(container);
return stream;
}
// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const std::pair<T1, T2> &value)
{
if( delimiters<std::pair<T1, T2>, TChar>::values.prefix != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.prefix;
stream << value.first;
if( delimiters<std::pair<T1, T2>, TChar>::values.delimiter != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.delimiter;
stream << value.second;
if( delimiters<std::pair<T1, T2>, TChar>::values.postfix != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.postfix;
return stream;
}
// Used by the sample below to generate some values
struct fibonacci
{
fibonacci() : f1(0), f2(1) { }
int operator()()
{
int r = f1 + f2;
f1 = f2;
f2 = r;
return f1;
}
private:
int f1;
int f2;
};
int main()
{
std::vector<int> v;
std::generate_n(std::back_inserter(v), 10, fibonacci());
std::cout << v << std::endl;
// Example of using pretty_ostream_iterator directly
std::generate_n(pretty_ostream_iterator<int>(std::cout, ";"), 20, fibonacci());
std::cout << std::endl;
}
Al igual que la versión de Marcelo, utiliza un rasgo de tipo is_container que debe ser especializado para todos los contenedores que se admitirán. Puede ser posible usar un rasgo para verificar value_type
, const_iterator
, begin()
/ end()
, pero no estoy seguro de que lo recomiendo ya que puede coincidir con cosas que coinciden con esos criterios pero no son en realidad contenedores, como std::basic_string
. También al igual que la versión de Marcelo, usa plantillas que pueden especializarse para especificar los delimitadores a usar.
La principal diferencia es que he construido mi versión en torno a un pretty_ostream_iterator
, que funciona de forma similar al std::ostream_iterator
pero no imprime un delimitador después del último elemento. El formateo de los contenedores lo realiza el print_container_helper
, que se puede usar directamente para imprimir contenedores sin un rasgo de is_container, o para especificar un tipo de delimitador diferente.
También he definido is_container y delimiters para que funcione con contenedores con predicados o asignadores no estándar, y para char y wchar_t. La función del operador << también se define para trabajar con las secuencias char y wchar_t.
Finalmente, he usado std::enable_if
, que está disponible como parte de C ++ 0x, y funciona en Visual C ++ 2010 y g ++ 4.3 (necesita la marca -std = c ++ 0x) y versiones posteriores. De esta manera no hay dependencia en Boost.
Esto se ha editado varias veces, y hemos decidido llamar a la clase principal que envuelve una colección RangePrinter
Esto debería funcionar automáticamente con cualquier colección una vez que haya escrito la sobrecarga del operador por única vez << excepto que necesitará una especial para que los mapas impriman el par, y es posible que desee personalizar el delimitador allí.
También podría tener una función especial de "impresión" para usar en el artículo en lugar de simplemente enviarlo directamente. Un poco como los algoritmos STL le permiten pasar predicados personalizados. Con map lo usaría de esta manera, con una impresora personalizada para el std :: pair.
Su impresora "predeterminada" simplemente la enviará a la transmisión.
Ok, vamos a trabajar en una impresora personalizada. Cambiaré mi clase externa a RangePrinter. Tenemos 2 iteradores y algunos delimitadores, pero no hemos personalizado cómo imprimir los elementos reales.
struct DefaultPrinter
{
template< typename T >
std::ostream & operator()( std::ostream& os, const T& t ) const
{
return os << t;
}
// overload for std::pair
template< typename K, typename V >
std::ostream & operator()( std::ostream & os, std::pair<K,V> const& p)
{
return os << p.first << ''='' << p.second;
}
};
// some prototypes
template< typename FwdIter, typename Printer > class RangePrinter;
template< typename FwdIter, typename Printer >
std::ostream & operator<<( std::ostream &,
RangePrinter<FwdIter, Printer> const& );
template< typename FwdIter, typename Printer=DefaultPrinter >
class RangePrinter
{
FwdIter begin;
FwdIter end;
std::string delim;
std::string open;
std::string close;
Printer printer;
friend std::ostream& operator<< <>( std::ostream&,
RangePrinter<FwdIter,Printer> const& );
public:
RangePrinter( FwdIter b, FwdIter e, Printer p,
std::string const& d, std::string const & o, std::string const& c )
: begin( b ), end( e ), printer( p ), open( o ), close( c )
{
}
// with no "printer" variable
RangePrinter( FwdIter b, FwdIter e,
std::string const& d, std::string const & o, std::string const& c )
: begin( b ), end( e ), open( o ), close( c )
{
}
};
template<typename FwdIter, typename Printer>
std::ostream& operator<<( std::ostream& os,
RangePrinter<FwdIter, Printer> const& range )
{
const Printer & printer = range.printer;
os << range.open;
FwdIter begin = range.begin, end = range.end;
// print the first item
if (begin == end)
{
return os << range.close;
}
printer( os, *begin );
// print the rest with delim as a prefix
for( ++begin; begin != end; ++begin )
{
os << range.delim;
printer( os, *begin );
}
return os << range.close;
}
Ahora, de forma predeterminada, funcionará para mapas siempre y cuando los tipos de clave y valor sean imprimibles y usted pueda colocar su propia impresora de elementos especiales para cuando no lo estén (como puede hacerlo con cualquier otro tipo), o si no desea = como el delimitador.
Estoy moviendo la función libre para crear estos hasta el final ahora:
Una función libre (versión de iterador) se vería como algo así e incluso podría tener valores predeterminados:
template<typename Collection>
RangePrinter<typename Collection::const_iterator> rangePrinter
( const Collection& coll, const char * delim=",",
const char * open="[", const char * close="]")
{
return RangePrinter< typename Collection::const_iterator >
( coll.begin(), coll.end(), delim, open, close );
}
Entonces podrías usarlo para std :: set by
std::cout << outputFormatter( mySet );
También puede escribir una versión de función libre que tome una impresora personalizada y una que tome dos iteradores. En cualquier caso, resolverán los parámetros de la plantilla por usted, y usted podrá pasarlos como temporales.
El código demostró ser útil en varias ocasiones ahora y siento que el costo de personalización es muy bajo. Por lo tanto, decidí lanzarlo bajo la licencia MIT y proporcionar un repositorio GitHub donde se puedan descargar el encabezado y un pequeño archivo de ejemplo.
http://djmuw.github.io/prettycc
0. Prefacio y redacción.
Una "decoración" en términos de esta respuesta es un conjunto de cadena de prefijo, cadena de delimitador y una cadena de sufijo. Donde la cadena de prefijo se inserta en una secuencia antes y la cadena de postfix después de los valores de un contenedor (ver 2. Contenedores de destino). La cadena delimitadora se inserta entre los valores del contenedor respectivo.
Nota: en realidad, esta respuesta no aborda la pregunta al 100% ya que la decoración no es una constante de tiempo compilada estrictamente porque se requieren verificaciones de tiempo de ejecución para verificar si se ha aplicado una decoración personalizada a la secuencia actual. Sin embargo, creo que tiene algunas características decentes.
Nota 2: puede tener errores menores ya que aún no está bien probado.
1. Idea general / uso
Cero código adicional requerido para el uso
Se debe mantener tan fácil como
#include <vector>
#include "pretty.h"
int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
return 0;
}
Fácil personalización ...
... con respecto a un objeto de flujo específico
#include <vector>
#include "pretty.h"
int main()
{
// set decoration for std::vector<int> for cout object
std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
return 0;
}
o con respecto a todas las corrientes:
#include <vector>
#include "pretty.h"
// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")
int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
return 0;
}
Descripción aproximada
- El código incluye una plantilla de clase que proporciona una decoración predeterminada para cualquier tipo
- que puede ser especializado para cambiar la decoración por defecto para (a) cierto tipo (s) y es
- usando el almacenamiento privado provisto
ios_base
usandoxalloc
/pword
para guardar un puntero a unpretty::decor
objeto específicamente decorando cierto tipo en un flujo determinado
Si no pretty::decor<T>
se ha configurado explícitamente ningún objeto para este flujo, pretty::defaulted<T, charT, chartraitT>::decoration()
se llama para obtener la decoración predeterminada para el tipo dado. La clase pretty::defaulted
debe ser especializada para personalizar decoraciones por defecto.
2. Objetos objetivo / contenedores
Los objetos de destino obj
para la "decoración bonita" de este código son objetos que tienen
- sobrecargas
std::begin
ystd::end
definidas (incluye matrices estilo C), - Tener
begin(obj)
yend(obj)
disponible a través de ADL, - son de tipo
std::tuple
- o de tipo
std::pair
.
El código incluye un rasgo para la identificación de clases con características de rango ( begin
/ end
). (Sin begin(obj) == end(obj)
embargo, no se incluye ninguna verificación, ya sea una expresión válida).
El código proporciona operator<<
s en el espacio de nombres global que solo se aplica a las clases que no tienen una versión más especializada de operator<<
disponible. Por lo tanto, por ejemplo, std::string
no se imprime utilizando el operador en este código aunque tenga un par begin
/ válido end
.
3. Utilización y personalización.
Las decoraciones se pueden imponer por separado para cada tipo (excepto las diferentes tuple
s) y para la transmisión (¡no para el tipo de transmisión!). (Es decir, una std::vector<int>
lata puede tener diferentes decoraciones para diferentes objetos de corriente).
A) Decoración por defecto
El prefijo predeterminado es ""
(nada) como es el postfix predeterminado, mientras que el delimitador predeterminado es ", "
(coma + espacio).
B) Decoración predeterminada personalizada de un tipo especializando la pretty::defaulted
plantilla de clase
El struct defaulted
tiene una función miembro estática que decoration()
devuelve un decor
objeto que incluye los valores predeterminados para el tipo dado.
Ejemplo usando una matriz:
Personalice la impresión de matriz predeterminada:
namespace pretty
{
template<class T, std::size_t N>
struct defaulted<T[N]>
{
static decor<T[N]> decoration()
{
return{ { "(" }, { ":" }, { ")" } };
}
};
}
Imprimir una matriz arry:
float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
std::cout << e << ''/n''; // prints (3.4:4.3:5.2:1.1:22.2)
Usando la PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
macro para char
streams
La macro se expande a
namespace pretty {
template< __VA_ARGS__ >
struct defaulted< TYPE > {
static decor< TYPE > decoration() {
return { PREFIX, DELIM, POSTFIX };
}
};
}
permitiendo reescribir la especialización parcial anterior a
PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)
o insertando una especialización completa como
PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")
Otra macro para wchar_t
corrientes se incluye: PRETTY_DEFAULT_WDECORATION
.
C) Imponer decoración en arroyos.
La función pretty::decoration
se utiliza para imponer una decoración en un determinado flujo. Hay sobrecargas que toman cualquiera de ellas: un argumento de cadena es el delimitador (que adopta el prefijo y el postfijo de la clase predeterminada) o tres argumentos de cadena que reúnen la decoración completa
Decoración completa para tipo y flujo dado.
float e[3] = { 3.4f, 4.3f, 5.2f };
std::stringstream u;
// add { ; } decoration to u
u << pretty::decoration<float[3]>("{", "; ", "}");
// use { ; } decoration
u << e << ''/n''; // prints {3.4; 4.3; 5.2}
// uses decoration returned by defaulted<float[3]>::decoration()
std::cout << e; // prints 3.4, 4.3, 5.2
Personalización del delimitador para flujo dado.
PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")
std::stringstream v;
v << e; // prints {{{3.4,4.3,5.2}}}
v << pretty::decoration<float[3]>(":");
v << e; // prints {{{3.4:4.3:5.2}}}
v << pretty::decoration<float[3]>("((", "=", "))");
v << e; // prints ((3.4=4.3=5.2))
4. Manejo especial de std::tuple
En lugar de permitir una especialización para cada tipo de tupla posible, este código aplica cualquier decoración disponible para std::tuple<void*>
todo tipo de std::tuple<...>
s.
5. Eliminar la decoración personalizada de la corriente
Para volver a la decoración predeterminada para un tipo determinado, use la pretty::clear
plantilla de función en el flujo s
.
s << pretty::clear<std::vector<int>>();
5. Otros ejemplos
Impresión "tipo matriz" con delimitador de nueva línea
std::vector<std::vector<int>> m{ {1,2,3}, {4,5,6}, {7,8,9} };
std::cout << pretty::decoration<std::vector<std::vector<int>>>("/n");
std::cout << m;
Huellas dactilares
1, 2, 3
4, 5, 6
7, 8, 9
ideone/KKUebZ en ideone/KKUebZ
6. Código
#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_
#include <string>
#include <iostream>
#include <type_traits>
#include <iterator>
#include <utility>
#define PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) /
namespace pretty { template< __VA_ARGS__ >/
struct defaulted< TYPE > {/
static decor< TYPE > decoration(){/
return { PREFIX, DELIM, POSTFIX };/
} /*decoration*/ }; /*defaulted*/} /*pretty*/
#define PRETTY_DEFAULT_WDECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) /
namespace pretty { template< __VA_ARGS__ >/
struct defaulted< TYPE, wchar_t, std::char_traits<wchar_t> > {/
static decor< TYPE, wchar_t, std::char_traits<wchar_t> > decoration(){/
return { PREFIX, DELIM, POSTFIX };/
} /*decoration*/ }; /*defaulted*/} /*pretty*/
namespace pretty
{
namespace detail
{
// drag in begin and end overloads
using std::begin;
using std::end;
// helper template
template <int I> using _ol = std::integral_constant<int, I>*;
// SFINAE check whether T is a range with begin/end
template<class T>
class is_range
{
// helper function declarations using expression sfinae
template <class U, _ol<0> = nullptr>
static std::false_type b(...);
template <class U, _ol<1> = nullptr>
static auto b(U &v) -> decltype(begin(v), std::true_type());
template <class U, _ol<0> = nullptr>
static std::false_type e(...);
template <class U, _ol<1> = nullptr>
static auto e(U &v) -> decltype(end(v), std::true_type());
// return types
using b_return = decltype(b<T>(std::declval<T&>()));
using e_return = decltype(e<T>(std::declval<T&>()));
public:
static const bool value = b_return::value && e_return::value;
};
}
// holder class for data
template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
struct decor
{
static const int xindex;
std::basic_string<CharT, TraitT> prefix, delimiter, postfix;
decor(std::basic_string<CharT, TraitT> const & pre = "",
std::basic_string<CharT, TraitT> const & delim = "",
std::basic_string<CharT, TraitT> const & post = "")
: prefix(pre), delimiter(delim), postfix(post) {}
};
template<class T, class charT, class traits>
int const decor<T, charT, traits>::xindex = std::ios_base::xalloc();
namespace detail
{
template<class T, class CharT, class TraitT>
void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx)
{
using deco_type = decor<T, CharT, TraitT>;
if (evt == std::ios_base::erase_event)
{ // erase deco
void const * const p = s.pword(idx);
if (p)
{
delete static_cast<deco_type const * const>(p);
s.pword(idx) = nullptr;
}
}
else if (evt == std::ios_base::copyfmt_event)
{ // copy deco
void const * const p = s.pword(idx);
if (p)
{
auto np = new deco_type{ *static_cast<deco_type const * const>(p) };
s.pword(idx) = static_cast<void*>(np);
}
}
}
template<class T> struct clearer {};
template<class T, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT>& operator<< (
std::basic_ostream<CharT, TraitT> &s, clearer<T> const &)
{
using deco_type = decor<T, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
if (p)
{ // delete if set
delete static_cast<deco_type const *>(p);
s.pword(deco_type::xindex) = nullptr;
}
return s;
}
template <class CharT>
struct default_data { static const CharT * decor[3]; };
template <>
const char * default_data<char>::decor[3] = { "", ", ", "" };
template <>
const wchar_t * default_data<wchar_t>::decor[3] = { L"", L", ", L"" };
}
// Clear decoration for T
template<class T>
detail::clearer<T> clear() { return{}; }
template<class T, class CharT, class TraitT>
void clear(std::basic_ostream<CharT, TraitT> &s) { s << detail::clearer<T>{}; }
// impose decoration on ostream
template<class T, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT>& operator<<(
std::basic_ostream<CharT, TraitT> &s, decor<T, CharT, TraitT> && h)
{
using deco_type = decor<T, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
// delete if already set
if (p) delete static_cast<deco_type const *>(p);
s.pword(deco_type::xindex) = static_cast<void *>(new deco_type{ std::move(h) });
// check whether we alread have a callback registered
if (s.iword(deco_type::xindex) == 0)
{ // if this is not the case register callback and set iword
s.register_callback(detail::manage_decor<T, CharT, TraitT>, deco_type::xindex);
s.iword(deco_type::xindex) = 1;
}
return s;
}
template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
struct defaulted
{
static inline decor<T, CharT, TraitT> decoration()
{
return{ detail::default_data<CharT>::decor[0],
detail::default_data<CharT>::decor[1],
detail::default_data<CharT>::decor[2] };
}
};
template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
decor<T, CharT, TraitT> decoration(
std::basic_string<CharT, TraitT> const & prefix,
std::basic_string<CharT, TraitT> const & delimiter,
std::basic_string<CharT, TraitT> const & postfix)
{
return{ prefix, delimiter, postfix };
}
template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
decor<T, CharT, TraitT> decoration(
std::basic_string<CharT, TraitT> const & delimiter)
{
using str_type = std::basic_string<CharT, TraitT>;
return{ defaulted<T, CharT, TraitT>::decoration().prefix,
delimiter, defaulted<T, CharT, TraitT>::decoration().postfix };
}
template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
decor<T, CharT, TraitT> decoration(CharT const * const prefix,
CharT const * const delimiter, CharT const * const postfix)
{
using str_type = std::basic_string<CharT, TraitT>;
return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } };
}
template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
decor<T, CharT, TraitT> decoration(CharT const * const delimiter)
{
using str_type = std::basic_string<CharT, TraitT>;
return{ defaulted<T, CharT, TraitT>::decoration().prefix,
str_type{ delimiter }, defaulted<T, CharT, TraitT>::decoration().postfix };
}
template<typename T, std::size_t N, std::size_t L>
struct tuple
{
template<class CharT, class TraitT>
static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
std::basic_string<CharT, TraitT> const &delimiter)
{
s << std::get<N>(value) << delimiter;
tuple<T, N + 1, L>::print(s, value, delimiter);
}
};
template<typename T, std::size_t N>
struct tuple<T, N, N>
{
template<class CharT, class TraitT>
static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
std::basic_string<CharT, TraitT> const &) {
s << std::get<N>(value);
}
};
}
template<class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
std::basic_ostream<CharT, TraitT> &s, std::tuple<> const & v)
{
using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
auto const d = static_cast<deco_type const * const>(p);
s << (d ? d->prefix : defaulted_type::decoration().prefix);
s << (d ? d->postfix : defaulted_type::decoration().postfix);
return s;
}
template<class CharT, class TraitT, class ... T>
std::basic_ostream<CharT, TraitT> & operator<< (
std::basic_ostream<CharT, TraitT> &s, std::tuple<T...> const & v)
{
using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
using pretty_tuple = pretty::tuple<std::tuple<T...>, 0U, sizeof...(T)-1U>;
void const * const p = s.pword(deco_type::xindex);
auto const d = static_cast<deco_type const * const>(p);
s << (d ? d->prefix : defaulted_type::decoration().prefix);
pretty_tuple::print(s, v, d ? d->delimiter :
defaulted_type::decoration().delimiter);
s << (d ? d->postfix : defaulted_type::decoration().postfix);
return s;
}
template<class T, class U, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
std::basic_ostream<CharT, TraitT> &s, std::pair<T, U> const & v)
{
using deco_type = pretty::decor<std::pair<T, U>, CharT, TraitT>;
using defaulted_type = pretty::defaulted<std::pair<T, U>, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
auto const d = static_cast<deco_type const * const>(p);
s << (d ? d->prefix : defaulted_type::decoration().prefix);
s << v.first;
s << (d ? d->delimiter : defaulted_type::decoration().delimiter);
s << v.second;
s << (d ? d->postfix : defaulted_type::decoration().postfix);
return s;
}
template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
typename std::enable_if < pretty::detail::is_range<T>::value,
std::basic_ostream < CharT, TraitT >> ::type & operator<< (
std::basic_ostream<CharT, TraitT> &s, T const & v)
{
bool first(true);
using deco_type = pretty::decor<T, CharT, TraitT>;
using default_type = pretty::defaulted<T, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
auto d = static_cast<pretty::decor<T, CharT, TraitT> const * const>(p);
s << (d ? d->prefix : default_type::decoration().prefix);
for (auto const & e : v)
{ // v is range thus range based for works
if (!first) s << (d ? d->delimiter : default_type::decoration().delimiter);
s << e;
first = false;
}
s << (d ? d->postfix : default_type::decoration().postfix);
return s;
}
#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_
Aquí hay una biblioteca de trabajo, presentada como un programa de trabajo completo, que acabo de hackear juntos:
#include <set>
#include <vector>
#include <iostream>
#include <boost/utility/enable_if.hpp>
// Default delimiters
template <class C> struct Delims { static const char *delim[3]; };
template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"};
// Special delimiters for sets.
template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; };
template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"};
template <class C> struct IsContainer { enum { value = false }; };
template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; };
template <typename T> struct IsContainer< std::set<T> > { enum { value = true }; };
template <class C>
typename boost::enable_if<IsContainer<C>, std::ostream&>::type
operator<<(std::ostream & o, const C & x)
{
o << Delims<C>::delim[0];
for (typename C::const_iterator i = x.begin(); i != x.end(); ++i)
{
if (i != x.begin()) o << Delims<C>::delim[1];
o << *i;
}
o << Delims<C>::delim[2];
return o;
}
template <typename T> struct IsChar { enum { value = false }; };
template <> struct IsChar<char> { enum { value = true }; };
template <typename T, int N>
typename boost::disable_if<IsChar<T>, std::ostream&>::type
operator<<(std::ostream & o, const T (&x)[N])
{
o << "[";
for (int i = 0; i != N; ++i)
{
if (i) o << ",";
o << x[i];
}
o << "]";
return o;
}
int main()
{
std::vector<int> i;
i.push_back(23);
i.push_back(34);
std::set<std::string> j;
j.insert("hello");
j.insert("world");
double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 };
std::cout << i << "/n" << j << "/n" << k << "/n";
}
Actualmente solo funciona con vector
y set
, pero se puede hacer que funcione con la mayoría de los contenedores, simplemente ampliando las IsContainer
especializaciones. No he pensado mucho sobre si este código es mínimo, pero no puedo pensar de inmediato en algo que pudiera eliminar como redundante.
EDITAR: Sólo por diversión, incluí una versión que maneja arreglos. Tuve que excluir matrices de caracteres para evitar más ambigüedades; Todavía podría tener problemas con wchar_t[]
.
El objetivo aquí es usar ADL para hacer la personalización de cómo imprimimos.
Usted pasa una etiqueta del formateador y anula 4 funciones (antes, después, entre y desciende) en el espacio de nombres de la etiqueta. Esto cambia la forma en que el formateador imprime "adornos" al iterar sobre contenedores.
Un formateador predeterminado que hace {(a->b),(c->d)}
para mapas, (a,b,c)
para tupleoids, "hello"
para cadenas, [x,y,z]
para todo lo demás incluido.
Debe "simplemente trabajar" con tipos iterables de terceros (y tratarlos como "todo lo demás").
Si desea adornos personalizados para sus iterables de terceros, simplemente cree su propia etiqueta. Tomará un poco de trabajo manejar el descenso del mapa (debe sobrecargarse pretty_print_descend( your_tag
para regresar pretty_print::decorator::map_magic_tag<your_tag>
). Tal vez hay una forma más limpia de hacer esto, no estoy seguro.
Una pequeña biblioteca para detectar iterabilidad, y tuple-ness:
namespace details {
using std::begin; using std::end;
template<class T, class=void>
struct is_iterable_test:std::false_type{};
template<class T>
struct is_iterable_test<T,
decltype((void)(
(void)(begin(std::declval<T>())==end(std::declval<T>()))
, ((void)(std::next(begin(std::declval<T>()))))
, ((void)(*begin(std::declval<T>())))
, 1
))
>:std::true_type{};
template<class T>struct is_tupleoid:std::false_type{};
template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
// template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
}
template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};
template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};
Una biblioteca que nos permite visitar los contenidos de un objeto iterable o tipo tupla:
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_first(C&& c, F&& f) {
using std::begin; using std::end;
auto&& b = begin(c);
auto&& e = end(c);
if (b==e)
return;
std::forward<F>(f)(*b);
}
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_all_but_first(C&& c, F&& f) {
using std::begin; using std::end;
auto it = begin(c);
auto&& e = end(c);
if (it==e)
return;
it = std::next(it);
for( ; it!=e; it = std::next(it) ) {
f(*it);
}
}
namespace details {
template<class Tup, class F>
void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is, class Tup, class F>
void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) );
}
template<class Tup, class F>
void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is,class Tup, class F>
void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
int unused[] = {0,((void)(
f( std::get<Is>(std::forward<Tup>(tup)) )
),0)...};
(void)(unused);
}
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) {
details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) {
details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
Una bonita biblioteca de impresión:
namespace pretty_print {
namespace decorator {
struct default_tag {};
template<class Old>
struct map_magic_tag:Old {}; // magic for maps
// Maps get {}s. Write trait `is_associative` to generalize:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT(''{'');
}
template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT(''}'');
}
// tuples and pairs get ():
template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT(''('');
}
template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT('')'');
}
// strings with the same character type get ""s:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT(''"'');
}
template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT(''"'');
}
// and pack the characters together:
template<class CharT, class Traits, class...Xs >
void pretty_print_between( default_tag, std::basic_ostream<CharT, Traits>&, std::basic_string<CharT, Xs...> const& ) {}
// map magic. When iterating over the contents of a map, use the map_magic_tag:
template<class...Xs>
map_magic_tag<default_tag> pretty_print_descend( default_tag, std::map<Xs...> const& ) {
return {};
}
template<class old_tag, class C>
old_tag pretty_print_descend( map_magic_tag<old_tag>, C const& ) {
return {};
}
// When printing a pair immediately within a map, use -> as a separator:
template<class old_tag, class CharT, class Traits, class...Xs >
void pretty_print_between( map_magic_tag<old_tag>, std::basic_ostream<CharT, Traits>& s, std::pair<Xs...> const& ) {
s << CharT(''-'') << CharT(''>'');
}
}
// default behavior:
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_before( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT(''['');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_after( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT('']'');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_between( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT('','');
}
template<class Tag, class Container>
Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
return std::forward<Tag>(tag);
}
// print things by default by using <<:
template<class Tag=decorator::default_tag, class Scalar, class CharT, class Traits>
std::enable_if_t<!is_visitable<Scalar>{}> print( std::basic_ostream<CharT, Traits>& os, Scalar&& scalar, Tag&&=Tag{} ) {
os << std::forward<Scalar>(scalar);
}
// for anything visitable (see above), use the pretty print algorithm:
template<class Tag=decorator::default_tag, class C, class CharT, class Traits>
std::enable_if_t<is_visitable<C>{}> print( std::basic_ostream<CharT, Traits>& os, C&& c, Tag&& tag=Tag{} ) {
pretty_print_before( std::forward<Tag>(tag), os, std::forward<C>(c) );
visit_first( c, [&](auto&& elem) {
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
visit_all_but_first( c, [&](auto&& elem) {
pretty_print_between( std::forward<Tag>(tag), os, std::forward<C>(c) );
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
pretty_print_after( std::forward<Tag>(tag), os, std::forward<C>(c) );
}
}
Código de prueba:
int main() {
std::vector<int> x = {1,2,3};
pretty_print::print( std::cout, x );
std::cout << "/n";
std::map< std::string, int > m;
m["hello"] = 3;
m["world"] = 42;
pretty_print::print( std::cout, m );
std::cout << "/n";
}
Esto utiliza características de C ++ 14 (algunos _t
alias y auto&&
lambdas), pero ninguna es esencial.
Voy a agregar otra respuesta aquí, porque se me ocurrió un enfoque diferente al anterior, y es usar facetas de locale.
Lo básico está here
Esencialmente lo que haces es:
- Crear una clase que derive de
std::locale::facet
. El pequeño inconveniente es que necesitará una unidad de compilación en algún lugar para mantener su id. Llamémoslo MyPrettyVectorPrinter. Probablemente le darías un nombre mejor, y también crearías uno para el par y el mapa. - En su función de transmisión, usted verifica
std::has_facet< MyPrettyVectorPrinter >
- Si eso devuelve cierto, extraelo con
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
- Sus objetos de faceta tendrán valores para los delimitadores y usted puede leerlos. Si no se encuentra la faceta, su función de impresión (
operator<<
) proporciona las predeterminadas. Tenga en cuenta que puede hacer lo mismo para leer un vector.
Me gusta este método porque puede usar una impresión predeterminada mientras aún puede usar una anulación personalizada.
Las desventajas son la necesidad de una biblioteca para su faceta si se usa en múltiples proyectos (por lo que no puede ser solo de encabezados) y también el hecho de que debe tener cuidado con el costo de crear un nuevo objeto de configuración regional.
He escrito esto como una nueva solución en lugar de modificar mi otra porque creo que ambos enfoques pueden ser correctos y usted elige.