Observe as atualizações no final deste post.
Atualização: eu criei um projeto público no GitHub para esta biblioteca!
Gostaria de ter um único modelo que, de uma vez por todas, cuide da impressão bonita de todos os contêineres STL via operator<<
. No pseudo-código, estou procurando algo parecido com isto:
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;
}
Agora eu já vi muitas mágicas de modelos aqui no SO que nunca pensei serem possíveis, então estou pensando se alguém pode sugerir algo que corresponda a todos os contêineres C. Talvez algo que possa descobrir se algo tem o iterador necessário ?
Muito Obrigado!
Atualização (e solução)
Depois de levantar esse problema novamente no canal 9 , recebi uma resposta fantástica de Sven Groot, que, combinada com um pouco do traço do tipo SFINAE, parece resolver o problema de uma maneira completamente geral e aninhada. Os delimitadores podem ser especializados individualmente, incluindo um exemplo de especialização para std :: set e um exemplo de uso de delimitadores personalizados.
O auxiliar "wrap_array ()" pode ser usado para imprimir matrizes C brutas. Atualização: pares e tuplas estão disponíveis para impressão; delimitadores padrão são colchetes.
A característica do tipo enable-if requer C ++ 0x, mas com algumas modificações, deve ser possível criar uma versão C ++ 98 disso. As tuplas requerem modelos variados, portanto, C ++ 0x.
Pedi a Sven para postar a solução aqui para que eu possa aceitá-la, mas, enquanto isso, gostaria de postar o código para referência. ( Atualização: Sven agora publicou seu código abaixo, que eu fiz a resposta aceita. Meu próprio código usa características de tipo de contêiner, que funcionam para mim, mas podem causar comportamento inesperado com classes sem contêiner que fornecem iteradores.)
Cabeçalho (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
Exemplo 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
;
}
Mais ideias para melhorias:
Implementar saídaAtualização: Esta é agora uma pergunta separada no SO ! Atualização: Isso foi implementado agora, graças ao Xeo!std::tuple<...>
da mesma maneira que a temosstd::pair<S,T>
.Adicione espaços para nome para que as classes auxiliares não sangrem no espaço para nome global.Feito- Adicione aliases de modelo (ou algo semelhante) para facilitar a criação de classes delimitadoras personalizadas ou talvez macros de pré-processador?
Atualizações recentes:
- Eu removi o iterador de saída personalizado em favor de um loop for simples na função de impressão.
- Todos os detalhes da implementação estão agora no
pretty_print
espaço para nome. Somente os operadores globais de fluxo e ospretty_print_array
wrapper estão no espaço para nome global. - Corrigido o namespace para que
operator<<
agora esteja corretamente inseridostd
.
Notas:
- A remoção do iterador de saída significa que não há como usar
std::copy()
para obter uma impressão bonita. Eu poderia restabelecer o iterador bonito se esse for um recurso desejado, mas o código de Sven abaixo tem a implementação. - Foi uma decisão consciente do projeto tornar os delimitadores constantes em tempo de compilação em vez de constantes em objetos. Isso significa que você não pode fornecer delimitadores dinamicamente em tempo de execução, mas também significa que não há sobrecarga desnecessária. Uma configuração de delimitador baseado em objeto foi proposta por Dennis Zickefoose em um comentário ao código de Sven abaixo. Se desejado, isso pode ser implementado como um recurso alternativo.
- Atualmente, não é óbvio como personalizar delimitadores de contêineres aninhados.
- Lembre-se de que o objetivo desta biblioteca é permitir instalações rápidas de impressão de contêineres que exigem zero codificação de sua parte. Não é uma biblioteca de formatação para todos os fins, mas uma ferramenta de desenvolvimento para aliviar a necessidade de escrever código de placa de caldeira para inspeção de contêineres.
Obrigado a todos que contribuíram!
Nota: Se você está procurando uma maneira rápida de implantar delimitadores personalizados, aqui está uma maneira de usar o apagamento de tipo. Assumimos que você já construiu uma classe delimitadora, digamos MyDel
assim:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Agora queremos poder escrever std::cout << MyPrinter(v) << std::endl;
para algum contêiner v
usando esses delimitadores. MyPrinter
será uma classe de apagamento de tipo, assim:
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); }
fonte
pretty_print
espaço para nome e fornecer um invólucro para o usuário usar ao imprimir. Do ponto de vista do usuário:std::cout << pretty_print(v);
(provavelmente com um nome diferente). Em seguida, você pode fornecer o operador no mesmo espaço de nome que o wrapper e, em seguida, pode expandir para imprimir de maneira bonita o que quiser. Você também pode melhorar o wrapper permitindo definir opcionalmente o separador para uso dentro de cada chamada (em vez de usar traços que forçam a mesma escolha para toda a aplicação) \Respostas:
Essa solução foi inspirada na solução de Marcelo, com algumas alterações:
Como a versão de Marcelo, ele usa uma característica do tipo is_container que deve ser especializada para todos os contêineres que devem ser suportados. Pode ser possível usar um traço para verificar se há
value_type
,const_iterator
,begin()
/end()
, mas não tenho certeza eu recomendo que uma vez que pode coincidir com as coisas que correspondem a esses critérios, mas não são realmente recipientes, comostd::basic_string
. Também como a versão de Marcelo, ele usa modelos que podem ser especializados para especificar os delimitadores a serem usados.A principal diferença é que eu construí minha versão em torno de a
pretty_ostream_iterator
, que funciona de maneira semelhante à,std::ostream_iterator
mas não imprime um delimitador após o último item. A formatação dos contêineres é feita peloprint_container_helper
, que pode ser usada diretamente para imprimir contêineres sem a característica is_container ou para especificar um tipo de delimitador diferente.Também defini is_container e delimiters, para que funcione para contêineres com predicados ou alocadores não padrão e para char e wchar_t. A própria função << do operador também é definida para funcionar com os fluxos char e wchar_t.
Finalmente, eu usei
std::enable_if
, que está disponível como parte do C ++ 0x, e funciona no Visual C ++ 2010 eg g ++ 4.3 (precisa do sinalizador -std = c ++ 0x) e posterior. Dessa forma, não há dependência do Boost.fonte
<i, j>
em uma função e[i j]
em outra, você precisa definir um tipo totalmente novo, com um punhado de membros estáticos para passar esse tipo paraprint_container_helper
? Isso parece excessivamente complexo. Por que não usar um objeto real, com campos que você pode definir caso a caso e as especializações simplesmente fornecendo valores padrão diferentes?print_container_helper
não é tão elegante quanto apenas ooperator<<
. Você sempre pode alterar a fonte, é claro, ou apenas adicionar especializações explícitas para o seu contêiner favorito, por exemplo, parapair<int, int>
e parapair<double, string>
. Em última análise, é uma questão de ponderar o poder contra a conveniência. Sugestões para melhorias são bem-vindas!MyDels
, posso dizerstd::cout << CustomPrinter<MyDels>(x);
. O que não posso fazer no momento é dizerstd::cout << CustomDelims<"{", ":", "}">(x);
, porque você não pode terconst char *
argumentos de modelo. A decisão de tornar os delimitadores constantes em tempo de compilação impõe algumas restrições à facilidade de uso, mas acho que vale a pena.Isso foi editado algumas vezes e decidimos chamar a classe principal que agrupa uma coleção RangePrinter
Isso deve funcionar automaticamente com qualquer coleção depois que você escrever a sobrecarga << do operador única <<, exceto que você precisará de uma especial para mapas para imprimir o par e talvez queira personalizar o delimitador lá.
Você também pode ter uma função especial de "impressão" para usar no item, em vez de apenas enviá-lo diretamente. Um pouco como os algoritmos STL permitem transmitir predicados personalizados. Com o mapa, você o usaria dessa maneira, com uma impressora personalizada para o std :: pair.
Sua impressora "padrão" enviaria apenas para o fluxo.
Ok, vamos trabalhar em uma impressora personalizada. Vou mudar minha classe externa para RangePrinter. Portanto, temos 2 iteradores e alguns delimitadores, mas não personalizamos como imprimir os itens reais.
Agora, por padrão, ele funcionará para mapas, desde que os tipos de chave e valor sejam imprimíveis e você possa colocar em sua própria impressora de itens especiais para quando não estiverem (como você pode com qualquer outro tipo) ou se não desejar = como o delimitador.
Estou movendo a função livre para criá-los até o fim agora:
Uma função livre (versão do iterador) se pareceria com algo assim e você pode até ter padrões:
Você poderia usá-lo para std :: set por
Você também pode escrever a versão de função livre que usa uma impressora personalizada e aquelas que usam dois iteradores. De qualquer forma, eles resolverão os parâmetros do modelo para você e você poderá transmiti-los como temporários.
fonte
std::cout << outputFormatter(beginOfRange, endOfRange);
.std::pair
é o exemplo mais básico de "coleção interna".std::map
facilmente com s e se funciona para coleções de coleções? Estou tentado a aceitar este como resposta, no entanto. Espero que Marcelo não se importe, sua solução também funciona.Aqui está uma biblioteca de trabalho, apresentada como um programa de trabalho completo, que eu acabei de hackear:
Atualmente, ele só funciona com
vector
eset
, mas pode ser feito para funcionar com a maioria dos contêineres, apenas expandindo asIsContainer
especializações. Não pensei muito sobre se esse código é mínimo, mas não consigo pensar imediatamente em algo que eu poderia retirar como redundante.Edição: Apenas para chutes, eu incluí uma versão que lida com matrizes. Eu tive que excluir matrizes de caracteres para evitar ambiguidades adicionais; ainda pode ter problemas
wchar_t[]
.fonte
std::map<>
especializando o operador ou definindo umoperator<<
parastd::pair<>
.Delims
modelo de classe!operator<<
modelo corresponder a praticamente qualquer coisa.Você pode formatar contêineres e intervalos e tuplas usando a biblioteca {fmt} . Por exemplo:
impressões
para
stdout
.Isenção de responsabilidade : sou o autor de {fmt}.
fonte
O código provou ser útil em várias ocasiões agora e sinto a despesa de entrar na personalização, pois o uso é bastante baixo. Assim, decidi liberá-lo sob licença MIT e fornecer um repositório GitHub onde o cabeçalho e um pequeno arquivo de exemplo podem ser baixados.
http://djmuw.github.io/prettycc
0. Prefácio e redação
Uma 'decoração' em termos desta resposta é um conjunto de prefixo, delimitador e postfix. Onde a sequência de prefixos é inserida em um fluxo antes e a sequência de pós-fixos após os valores de um contêiner (consulte 2. Contêineres de destino). A cadeia delimitadora é inserida entre os valores do respectivo contêiner.
Nota: Na verdade, esta resposta não aborda a pergunta a 100%, pois a decoração não é compilada estritamente constante de tempo, porque são necessárias verificações de tempo de execução para verificar se uma decoração personalizada foi aplicada ao fluxo atual. No entanto, acho que tem algumas características decentes.
Nota2: Pode haver pequenos erros, pois ainda não foi bem testado.
1. Ideia / uso geral
Zero código adicional necessário para uso
Deve ser mantido tão fácil quanto
Fácil personalização ...
... com relação a um objeto de fluxo específico
ou com relação a todos os fluxos:
Descrição aproximada
ios_base
usandoxalloc
/pword
para salvar um ponteiro em umpretty::decor
objeto que decora especificamente um determinado tipo em um determinado fluxo.Se nenhum
pretty::decor<T>
objeto para esse fluxo tiver sido configurado explicitamente,pretty::defaulted<T, charT, chartraitT>::decoration()
é chamado para obter a decoração padrão para o tipo especificado. A classepretty::defaulted
deve ser especializada para personalizar decorações padrão.2. Objetar objetos / contêineres
Objetos de destino
obj
para a 'decoração bonita' deste código são objetos que possuemstd::begin
estd::end
definidas (inclui matrizes C-Style),begin(obj)
eend(obj)
disponível via ADL,std::tuple
std::pair
.O código inclui uma característica para identificação de classes com recursos de intervalo (
begin
/end
). (Não há verificação incluída, sebegin(obj) == end(obj)
é uma expressão válida.)O código fornece
operator<<
s no espaço para nome global que se aplica apenas a classes que não possuem uma versão mais especializadaoperator<<
disponível. Portanto, por exemplo,std::string
não é impresso usando o operador neste código, embora tenha um parbegin
/ válidoend
.3. Utilização e customização
As decorações podem ser impostas separadamente para cada tipo (exceto
tuple
s diferentes ) e fluxo (não tipo de fluxo!). (Ou seja, umstd::vector<int>
pode ter decorações diferentes para diferentes objetos de fluxo.)A) decoração padrão
O prefixo padrão é
""
(nada) como é o postfix padrão, enquanto o delimitador padrão é", "
(vírgula + espaço).B) Decoração padrão personalizada de um tipo, especializando o
pretty::defaulted
modelo de classeO
struct defaulted
possui uma função de membro estáticodecoration()
retornando umdecor
objeto que inclui os valores padrão para o tipo especificado.Exemplo usando uma matriz:
Personalize a impressão em matriz padrão:
Imprima uma matriz arry:
Usando a
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
macro parachar
fluxosA macro se expande para
permitindo que a especialização parcial acima seja reescrita para
ou inserir uma especialização completa como
Outra macro para
wchar_t
fluxos está incluído:PRETTY_DEFAULT_WDECORATION
.C) Impor decoração em riachos
A função
pretty::decoration
é usada para impor uma decoração a um determinado fluxo. Existem sobrecargas que levam um - um argumento de string sendo o delimitador (adotando prefixo e postfix da classe padrão) - ou três argumentos de string montando a decoração completaDecoração completa para determinado tipo e fluxo
Customização do delimitador para determinado fluxo
4. Manuseio especial de
std::tuple
Em vez de permitir uma especialização para todos os tipos de tuplas possíveis, esse código aplica qualquer decoração disponível para
std::tuple<void*>
todos os tipos destd::tuple<...>
s.5. Remova a decoração personalizada do fluxo
Para voltar à decoração padrão para um determinado tipo, use o
pretty::clear
modelo de função no fluxos
.5. Outros exemplos
Impressão "matricial" com delimitador de nova linha
Impressões
Veja-o em ideone / KKUebZ
6. Código
fonte
Vou adicionar outra resposta aqui, porque criei uma abordagem diferente da anterior, que é usar facetas de localidade.
O básico está aqui
Essencialmente, o que você faz é:
std::locale::facet
. A pequena desvantagem é que você precisará de uma unidade de compilação em algum lugar para manter seu ID. Vamos chamá-lo de MyPrettyVectorPrinter. Você provavelmente daria um nome melhor e também criaria nomes para pares e mapas.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
operator<<
) fornecerá as padrão. Observe que você pode fazer o mesmo para ler um vetor.Eu gosto desse método porque você pode usar uma impressão padrão enquanto ainda pode usar uma substituição personalizada.
As desvantagens estão precisando de uma biblioteca para sua faceta se usada em vários projetos (portanto, não podem ser apenas apenas cabeçalhos) e também o fato de que você precisa tomar cuidado com as despesas de criação de um novo objeto de localidade.
Eu escrevi isso como uma nova solução, em vez de modificar a minha outra, porque acredito que ambas as abordagens podem estar corretas e você escolhe.
fonte
O objetivo aqui é usar o ADL para personalizar a forma como imprimimos.
Você passa uma tag do formatador e substitui 4 funções (antes, depois, entre e descende) no espaço de nomes da tag. Isso muda como o formatador imprime 'adornos' ao iterar sobre contêineres.
Um formatador padrão que serve
{(a->b),(c->d)}
para mapas,(a,b,c)
para tupleóides,"hello"
para strings e[x,y,z]
para todo o resto incluído.Ele deve "apenas funcionar" com tipos iteráveis de terceiros (e tratá-los como "tudo o resto").
Se você deseja adornos personalizados para iterables de terceiros, basta criar sua própria tag. Vai demorar um pouco de trabalho para lidar com a descida do mapa (você precisa sobrecarregar
pretty_print_descend( your_tag
para retornarpretty_print::decorator::map_magic_tag<your_tag>
). Talvez haja uma maneira mais limpa de fazer isso, não tenho certeza.Uma pequena biblioteca para detectar iterabilidade e tupla-ness:
Uma biblioteca que nos permite visitar o conteúdo de um objeto iterável ou de tupla:
Uma bonita biblioteca de impressão:
Código do teste:
exemplo ao vivo
Isso usa recursos do C ++ 14 (alguns
_t
aliases eauto&&
lambdas), mas nenhum é essencial.fonte
->
dentro depair
s demap
s) neste momento. O núcleo da bonita biblioteca de impressão é agradável e pequeno, o que é bom. Tentei torná-lo facilmente extensível, sem saber se consegui.Minha solução é simple.h , que faz parte do pacote scc . Todos os contêineres padrão, mapas, conjuntos, matrizes c são imprimíveis.
fonte
i
?std::set
com um comparador personalizado ou um mapa não-ordenado com uma igualdade personalizada. Seria muito importante apoiar essas construções.Saindo de um dos primeiros BoostCon (agora chamado CppCon), eu e mais dois outros trabalhamos em uma biblioteca para fazer exatamente isso. O principal ponto de discórdia era a necessidade de estender o namespace std. Isso acabou por ser uma proibição para uma biblioteca de impulso.
Infelizmente, os links para o código não funcionam mais, mas você pode encontrar alguns boatos interessantes nas discussões (pelo menos aqueles que não estão falando sobre o nome dele!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
fonte
Aqui está a minha versão da implementação feita em 2016
Tudo em um cabeçalho, portanto, é fácil usar https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
fonte