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 plantilla única que de una vez por todas se encargue de imprimir de manera bonita todos los contenedores STL operator<<
. En pseudocó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 mucha magia de plantillas aquí en SO que nunca pensé posible, así que me pregunto si alguien puede sugerir algo que coincida con todos los contenedores C. Quizás algo característico que pueda descubrir si algo tiene el iterador necesario ?
¡Muchas gracias!
Actualización (y solución)
Después de plantear este problema nuevamente en el Canal 9 , recibí una respuesta fantástica de Sven Groot, que, combinado con un poco de caracterización tipo SFINAE, parece resolver el problema de una manera completamente general y anidable. Los delimitadores pueden estar especializados individualmente, se incluye un ejemplo de especialización para std :: set, así como un ejemplo del uso de delimitadores personalizados.
El asistente "wrap_array ()" se puede usar para imprimir matrices C sin procesar. Actualización: pares y tuplas están disponibles para imprimir; los delimitadores predeterminados son corchetes.
El rasgo de tipo enable-if requiere C ++ 0x, pero con algunas modificaciones debería ser posible hacer una versión 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 yo mismo como 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 deActualización: ¡ Esta es ahora una pregunta separada sobre SO ! Actualización: ¡ Esto ahora se ha implementado, gracias a Xeo!std::tuple<...>
la misma manera para la que la tenemosstd::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 de delimitador personalizadas o quizás macros de preprocesador?
Actualizaciones recientes:
- Eliminé el iterador de salida personalizado a favor de un bucle for simple en la función de impresión.
- Todos los detalles de implementación están ahora en el
pretty_print
espacio de nombres. Solo los operadores de flujo global y elpretty_print_array
contenedor están en el espacio de nombres global. - Se corrigió el espacio de nombres para que
operator<<
ahora esté correctamente enstd
.
Notas:
- Eliminar el iterador de salida significa que no hay forma de usarlo
std::copy()
para obtener una impresión bonita. Podría restablecer el bonito iterador si esta es una característica deseada, pero el siguiente código de Sven tiene la implementación. - Fue una decisión de diseño consciente hacer que los delimitadores compilaran constantes de tiempo 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 sobrecarga innecesaria. Dennis Zickefoose propuso 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 rápida de contenedores que requieren cero codificación por su parte. No es una biblioteca de formato de uso múltiple, sino más bien una herramienta de desarrollo para aliviar la necesidad de escribir código de placa de 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, esta es una forma de usar el borrado de tipo. Suponemos que ya ha construido una clase de delimitador, por ejemplo MyDel
:
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); }
fuente
pretty_print
espacio de nombres y proporcionar un contenedor para que el usuario lo use al imprimir. Desde el punto de vista del usuario:std::cout << pretty_print(v);
(probablemente con un nombre diferente). Luego puede proporcionar el operador en el mismo espacio de nombres que el contenedor, y luego puede expandirse para imprimir lo que desee. También podría mejorar el envoltorio permitiendo opcionalmente definir el separador para usar dentro de cada llamada (en lugar de usar rasgos que fuercen la misma opción para toda la aplicación) \Respuestas:
Esta solución se inspiró en la solución de Marcelo, con algunos cambios:
Al igual que la versión de Marcelo, utiliza un rasgo de tipo is_container que debe estar especializado para todos los contenedores que se admitirán. Puede ser posible utilizar un rasgo para comprobar
value_type
,const_iterator
,begin()
/end()
, pero no estoy seguro de que lo recomiendo que ya que podría coincidir con las cosas que responden a esos criterios, pero en realidad no son contenedores, al igual questd::basic_string
. También, como la versión de Marcelo, utiliza plantillas que pueden especializarse para especificar los delimitadores que se utilizarán.La principal diferencia es que he creado mi versión en torno a un
pretty_ostream_iterator
, que funciona de manera similar alstd::ostream_iterator
pero no imprime un delimitador después del último elemento. El formateo de los contenedores se realiza medianteprint_container_helper
, que se puede usar directamente para imprimir contenedores sin un rasgo is_container, o para especificar un tipo de delimitador diferente.También he definido is_container y delimitadores para que funcione para contenedores con predicados o asignadores no estándar, y para char y wchar_t. La función de operador << en sí misma también está definida para trabajar con 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 el indicador -std = c ++ 0x) y posteriores. De esta manera no hay dependencia en Boost.fuente
<i, j>
en una función y como[i j]
en otra, ¿tiene que definir un tipo completamente nuevo, con un puñado de miembros estáticos para pasar ese tipoprint_container_helper
? Eso parece demasiado complejo. ¿Por qué no elegir un objeto real, con campos que puede establecer caso por caso y las especializaciones simplemente proporcionan diferentes valores predeterminados?print_container_helper
no es tan elegante como solo eloperator<<
. Siempre puede cambiar la fuente, por supuesto, o simplemente agregar especializaciones explícitas para su contenedor favorito, por ejemplo, parapair<int, int>
y parapair<double, string>
. En última instancia, se trata de sopesar el poder contra la conveniencia. Sugerencias para mejorar ¡bienvenido!MyDels
, entonces puedo decirstd::cout << CustomPrinter<MyDels>(x);
. Lo que no puedo hacer en este momento es decirstd::cout << CustomDelims<"{", ":", "}">(x);
, porque no puedes tenerconst char *
argumentos de plantilla. La decisión de hacer que los delimitadores sean constantes en el tiempo de compilación impone algunas restricciones a la facilidad de uso, pero creo que vale la pena.Esto ha sido 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 único <<, excepto que necesitará una especial para los mapas para imprimir el par, y puede que desee personalizar el delimitador allí.
También podría tener una función especial "imprimir" para usar en el elemento 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 par std ::.
Su impresora "predeterminada" simplemente la enviará a la transmisión.
Ok, trabajemos en una impresora personalizada. Cambiaré mi clase externa a RangePrinter. Entonces tenemos 2 iteradores y algunos delimitadores, pero no hemos personalizado cómo imprimir los elementos reales.
Ahora, de manera predeterminada, funcionará para los mapas siempre que los tipos de clave y valor sean imprimibles y pueda colocar su propia impresora de elementos especiales para cuando no lo sean (como puede hacerlo con cualquier otro tipo), o si no desea = como delimitador.
Estoy moviendo la función libre para crearlos hasta el final ahora:
Una función libre (versión iteradora) se vería algo así e incluso podría tener valores predeterminados:
Luego puede usarlo para std :: establecido por
También puede escribir una versión de función libre que tome una impresora personalizada y otras que tomen dos iteradores. En cualquier caso, resolverán los parámetros de la plantilla por usted y podrá pasarlos como temporales.
fuente
std::cout << outputFormatter(beginOfRange, endOfRange);
.std::pair
Es el ejemplo más básico de "colección interior".std::map
s fácilmente y si funciona para colecciones de colecciones? Sin embargo, estoy tentado a aceptar esta como respuesta. Espero que a Marcelo no le importe, su solución también funciona.Aquí hay una biblioteca de trabajo, presentada como un programa de trabajo completo, que acabo de piratear juntos:
Actualmente solo funciona con
vector
yset
, pero se puede hacer que funcione con la mayoría de los contenedores, simplemente ampliando lasIsContainer
especializaciones. No he pensado mucho sobre si este código es mínimo, pero no puedo pensar de inmediato en nada que pueda eliminar como redundante.EDITAR: Solo por diversión, incluí una versión que maneja matrices. Tuve que excluir las matrices de caracteres para evitar más ambigüedades; aún podría meterse en problemas con
wchar_t[]
.fuente
std::map<>
especializando el operador o definiendo unoperator<<
forstd::pair<>
.Delims
la plantilla de clase!operator<<
plantilla coincide con casi cualquier cosa.Puede formatear contenedores, así como rangos y tuplas utilizando la biblioteca {fmt} . Por ejemplo:
huellas dactilares
a
stdout
.Descargo de responsabilidad : soy el autor de {fmt}.
fuente
El código demostró ser útil en varias ocasiones ahora y siento que el gasto en personalización es bastante bajo. Por lo tanto, decidí lanzarlo bajo licencia MIT y proporcionar un repositorio de GitHub donde se pueda 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 postfijo. Donde la cadena de prefijo se inserta en una secuencia antes y la cadena de postfijo después de los valores de un contenedor (ver 2. Contenedores de destino). La cadena delimitador 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 estrictamente compilada en tiempo constante 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
Debe mantenerse tan fácil como
Fácil personalización ...
... con respecto a un objeto de flujo específico
o con respecto a todas las transmisiones:
Descripción aproximada
ios_base
usandoxalloc
/pword
para guardar un puntero a unpretty::decor
objeto que decora específicamente un determinado tipo en un flujo determinado.Si no
pretty::decor<T>
se ha establecido explícitamente ningún objeto para esta secuencia,pretty::defaulted<T, charT, chartraitT>::decoration()
se llama para obtener la decoración predeterminada para el tipo dado. La clasepretty::defaulted
se especializará para personalizar decoraciones predeterminadas.2. Objetos / contenedores de destino
Los objetos de destino
obj
para la 'bonita decoración' de este código son objetos que tienenstd::begin
ystd::end
definidas (incluye matrices de estilo C),begin(obj)
yend(obj)
disponible a través de ADL,std::tuple
std::pair
.El código incluye un rasgo para la identificación de clases con características de rango (
begin
/end
). (Sinbegin(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 aplican a las clases que no tienen una versión más especializada deoperator<<
disponible. Por lo tanto, por ejemplostd::string
no se imprime usando el operador en este código a pesar de tener un válidobegin
/end
par.3. Utilización y personalización.
Las decoraciones se pueden imponer por separado para cada tipo (excepto diferentes
tuple
s) y stream (¡no tipo de stream!). (Es decir,std::vector<int>
puede tener diferentes decoraciones para diferentes objetos de flujo).A) Decoración predeterminada
El prefijo predeterminado es
""
(nada) como 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 claseEl
struct defaulted
tiene una función miembro estáticodecoration()
devolver undecor
objeto que incluye los valores predeterminados para el tipo dado.Ejemplo usando una matriz:
Personalice la impresión de matriz predeterminada:
Imprima una matriz de arry:
Usando la
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
macro parachar
flujosLa macro se expande a
permitiendo reescribir la especialización parcial anterior a
o insertando una especialización completa como
Se
wchar_t
incluye otra macro para transmisiones:PRETTY_DEFAULT_WDECORATION
.C) Imponer la decoración en los arroyos.
La función
pretty::decoration
se utiliza para imponer una decoración en una secuencia determinada. Hay sobrecargas que toman, ya sea un argumento de cadena que es el delimitador (adoptando el prefijo y el postfix de la clase predeterminada), o tres argumentos de cadena que ensamblan la decoración completaDecoración completa para el tipo y flujo dado
Personalización del delimitador para una secuencia dada
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 destd::tuple<...>
s.5. Eliminar la decoración personalizada de la secuencia
Para volver a la decoración predeterminada para un tipo determinado, use la
pretty::clear
plantilla de función en la secuencias
.5. Otros ejemplos
Impresión "matricial" con delimitador de nueva línea
Huellas dactilares
Véalo en ideone / KKUebZ
6. Código
fuente
Voy a agregar otra respuesta aquí, porque se me ocurrió un enfoque diferente al anterior, y eso es usar facetas locales.
Lo básico está aquí.
Esencialmente, lo que haces es:
std::locale::facet
. El pequeño inconveniente es que necesitará una unidad de compilación en algún lugar para mantener su identificación. Llamémoslo MyPrettyVectorPrinter. Probablemente le darías un nombre mejor, y también crearías uno para pares y mapas.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
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 solo pueden ser encabezados) y también el hecho de que debe tener cuidado con el costo de crear un nuevo objeto de entorno local.
He escrito esto como una nueva solución en lugar de modificar la otra porque creo que ambos enfoques pueden ser correctos y tú eliges.
fuente
El objetivo aquí es usar ADL para personalizar la forma en que imprimimos.
Pasa una etiqueta de formateador y anula 4 funciones (antes, después, entre y descender) en el espacio de nombres de la etiqueta. Esto cambia la forma en que el formateador imprime 'adornos' al iterar sobre los contenedores.
Un formateador predeterminado que funciona
{(a->b),(c->d)}
para mapas,(a,b,c)
para tupleoides,"hello"
para cadenas,[x,y,z]
para todo lo demás incluido.Debería "funcionar" 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 (necesita sobrecargar
pretty_print_descend( your_tag
para regresarpretty_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 la iterabilidad y la tupla:
Una biblioteca que nos permite visitar el contenido de un objeto de tipo iterable o tupla:
Una bonita biblioteca de impresión:
Código de prueba:
ejemplo en vivo
Esto usa características de C ++ 14 (algunos
_t
alias yauto&&
lambdas), pero ninguna es esencial.fuente
->
dentro de laspair
s demap
s) en este punto. El núcleo de la bonita biblioteca de impresión es agradable y pequeño, lo cual es agradable. Traté de hacerlo fácilmente extensible, no estoy seguro si lo logré.Mi solución es simple.h , que forma parte del paquete scc . Todos los contenedores estándar, mapas, conjuntos, matrices c son imprimibles.
fuente
i
?std::set
con un comparador personalizado, o un_orden_map con una igualdad personalizada. Sería muy importante apoyar esas construcciones.Al salir de uno de los primeros BoostCon (ahora llamado CppCon), yo y otros dos trabajamos en una biblioteca para hacer precisamente esto. El principal problema era la necesidad de ampliar el espacio de nombres estándar. Resultó ser un no-go para una biblioteca de impulso.
Desafortunadamente, los enlaces al código ya no funcionan, pero es posible que encuentres algunos datos interesantes en las discusiones (¡al menos aquellos que no hablan sobre cómo nombrarlo!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
fuente
Aquí está mi versión de implementación realizada en 2016
Todo en un encabezado, por lo que es fácil de usar https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
fuente