Recipientes C ++ STL com impressão bonita

389

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ída std::tuple<...>da mesma maneira que a temos std::pair<S,T>. Atualização: Esta é agora uma pergunta separada no SO ! Atualização: Isso foi implementado agora, graças ao Xeo!
  • 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_printespaç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 inserido std.

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 MyDelassim:

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 vusando esses delimitadores. MyPrinterserá 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); }
Kerrek SB
fonte
Seu código não funcionará. não existe tal palavra-chave como contêiner C
the_drow 31/01
31
@the_drow: Parece que o OP já sabe disso. Eles estão apenas indicando o que estão procurando.
Marcelo Cantos
De fato, eu só dei um exemplo de pseudo-código "moral". (Também deixei de lado o tipo de retorno, estou observando.) Para ter certeza, nem sei como tornar os delimitadores possíveis.
Kerrek SB 31/01
11
Outra alternativa seria colocar os operadores no pretty_printespaç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) \
David Rodríguez - dribeas
11
Por favor, transforme suas respostas "atualizadas" em respostas reais, em vez de fazer uma pergunta absurda.
einpoklum

Respostas:

82

Essa solução foi inspirada na solução de Marcelo, com algumas alterações:

#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;
}

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, como std::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_iteratormas 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.

Sven
fonte
Se estou lendo isso corretamente, para que um par seja impresso <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 para print_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?
Dennis Zickefoose
Veja da seguinte maneira: se houver vários delimitadores de que você gosta pessoalmente, você pode fazer algumas aulas com membros estáticos de uma vez por todas e usá-las. Claro que você está certo que o uso print_container_helpernão é tão elegante quanto apenas o operator<<. Você sempre pode alterar a fonte, é claro, ou apenas adicionar especializações explícitas para o seu contêiner favorito, por exemplo, para pair<int, int>e para pair<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!
Ker11k Kerrek SB
... e para acompanhar, se você já precisar de impressão situacional do mesmo tipo de dados em diferentes formatações, provavelmente precisará escrever pelo menos um pequeno invólucro de qualquer maneira. Essa não é uma biblioteca de formatação altamente configurável, mas sim uma biblioteca padrão de esforço sem esforço que magicamente permite imprimir contêineres sem pensar ... (Mas se você quiser mais flexibilidade global , provavelmente poderemos adicionar alguns #macros para criar os padrões fáceis de manipular).
Kerrek SB
O problema real é que, embora eu possa modificar facilmente print_container_helper para usar parâmetros para os delimitadores personalizados, não há realmente nenhuma maneira de especificar delimitadores para um contêiner interno (ou par) além de especializar o modelo de delimitadores. Conseguir isso seria muito complicado.
Sven
Estou quase conseguindo obter uma solução conveniente de delimitador personalizado usando apagamento de tipo. Se você já tem uma classe delimitadora MyDels, posso dizer std::cout << CustomPrinter<MyDels>(x);. O que não posso fazer no momento é dizer std::cout << CustomDelims<"{", ":", "}">(x);, porque você não pode ter const 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.
61111 Kerrek SB
22

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.

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;
}

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:

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 );
}

Você poderia usá-lo para std :: set por

 std::cout << outputFormatter( mySet );

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.

CashCow
fonte
Eu vejo. Isso é semelhante à ideia de Marcelo Cantos, não é? Vou tentar transformar isso em um exemplo de trabalho, obrigado!
Kerrek SB 31/01
Acho essa solução muito mais limpa que a de Marcelo e oferece a mesma flexibilidade. Eu gosto do aspecto que é preciso envolver explicitamente a saída em uma chamada de função. Para ser muito legal, você pode adicionar suporte para a saída direta de uma série de iteradores, para que eu possa fazer isso std::cout << outputFormatter(beginOfRange, endOfRange);.
Björn Pollex
11
@ CashCow: existe um problema com esta solução, ela não parece funcionar com coleções recursivas (ou seja, coleções de coleções). std::pairé o exemplo mais básico de "coleção interna".
Matthieu M. 31/01
Estou gostando muito desta resposta, pois ela não tem dependências e não precisa saber sobre os contêineres que suporta. Podemos descobrir se ele pode lidar std::mapfacilmente 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.
Kerrek SB 31/01
@Matthieu M. Depende de como você imprime a coleção interna. Se você apenas usar os << abrir << * iter << fechar, terá um problema com isso, mas se permitir que o usuário passe uma impressora personalizada, como sugeri, você poderá imprimir o que quiser.
precisa saber é o seguinte
14

Aqui está uma biblioteca de trabalho, apresentada como um programa de trabalho completo, que eu acabei de hackear:

#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";
}

Atualmente, ele só funciona com vectore set, mas pode ser feito para funcionar com a maioria dos contêineres, apenas expandindo as IsContainerespecializaçõ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[].

Marcelo Cantos
fonte
2
@Nawaz: Como eu disse, este é apenas o começo de uma solução. Você poderia oferecer suporte std::map<>especializando o operador ou definindo um operator<<para std::pair<>.
Marcelo Cantos
No entanto, +1 para usar o Delimsmodelo de classe!
Nawaz 31/01
@ MC: Oh bom. Isso parece muito promissor! (By the way, você precisa de retorno tipo "std :: ostream &", eu tinha esquecido que, inicialmente.)
Kerrek SB
Hmm, eu recebo "sobrecarga ambígua" ao tentar isso em um std :: vector <int> e std :: set <std :: string> ...
Kerrek SB
Sim, atualmente estou descobrindo como evitar as ambiguidades, causadas pelo fato de o operator<<modelo corresponder a praticamente qualquer coisa.
Marcelo Cantos
10

Você pode formatar contêineres e intervalos e tuplas usando a biblioteca {fmt} . Por exemplo:

#include <vector>
#include <fmt/ranges.h>

int main() {
  auto v = std::vector<int>{1, 2, 3};
  fmt::print("{}", v);
}

impressões

{1, 2, 3}

para stdout.

Isenção de responsabilidade : sou o autor de {fmt}.

vitaut
fonte
8

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

#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 personalização ...

... com relação a um objeto de fluxo 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;
}

ou com relação a todos os fluxos:

#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;
}

Descrição aproximada

  • O código inclui um modelo de classe que fornece uma decoração padrão para qualquer tipo
  • que pode ser especializada para alterar a decoração padrão para (a) certos tipos e é
  • usando o armazenamento privado fornecido ios_baseusando xalloc/ pwordpara salvar um ponteiro em um pretty::decorobjeto 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 objpara a 'decoração bonita' deste código são objetos que possuem

  • sobrecargas std::beginestd::end definidas (inclui matrizes C-Style),
  • tendo begin(obj)e end(obj)disponível via ADL,
  • são do tipo std::tuple
  • ou do tipo 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, se begin(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 especializada operator<<disponível. Portanto, por exemplo, std::stringnão é impresso usando o operador neste código, embora tenha um par begin/ válido end.

3. Utilização e customização

As decorações podem ser impostas separadamente para cada tipo (exceto tuples 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::defaultedmodelo de classe

O struct defaultedpossui uma função de membro estático decoration()retornando um decorobjeto que inclui os valores padrão para o tipo especificado.

Exemplo usando uma matriz:

Personalize a impressão em matriz padrão:

namespace pretty
{
  template<class T, std::size_t N>
  struct defaulted<T[N]>
  {
    static decor<T[N]> decoration()
    {
      return{ { "(" }, { ":" }, { ")" } };
    }
  };
}

Imprima uma 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 a PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)macro para charfluxos

A macro se expande para

namespace pretty { 
  template< __VA_ARGS__ >
  struct defaulted< TYPE > {
    static decor< TYPE > decoration() {
      return { PREFIX, DELIM, POSTFIX };
    } 
  }; 
} 

permitindo que a especialização parcial acima seja reescrita para

PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)

ou inserir uma especialização completa como

PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")

Outra macro para wchar_tfluxos 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 completa

Decoração completa para determinado tipo e fluxo

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

Customização do delimitador para determinado fluxo

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. 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 de std::tuple<...>s.

5. Remova a decoração personalizada do fluxo

Para voltar à decoração padrão para um determinado tipo, use o pretty::clearmodelo de função no fluxo s.

s << pretty::clear<std::vector<int>>();

5. Outros exemplos

Impressão "matricial" com delimitador de nova linha

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;

Impressões

1, 2, 3
4, 5, 6
7, 8, 9

Veja-o em 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_
Pixelchemist
fonte
4

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 é:

  1. Crie uma classe que deriva de 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.
  2. Na sua função de fluxo, você verifica std::has_facet< MyPrettyVectorPrinter >
  3. Se isso retornar verdadeiro, extraia-o com std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Seus objetos de faceta terão valores para os delimitadores e você poderá lê-los. Se a faceta não for encontrada, sua função de impressão ( 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.

CashCow
fonte
Deixe-me ver se entendi: Com essa abordagem, preciso colocar na lista de permissões ativamente cada tipo de contêiner que quero usar?
21413 Kerrek SB
Bem, na verdade, não se deve estender std, exceto os próprios tipos, mas você escreve uma sobrecarga de operador << para cada tipo de contêiner (vetor, mapa, lista, deque) mais o par que deseja poder imprimir. É claro que alguns podem compartilhar uma faceta (por exemplo, você pode imprimir a lista, o vetor e o deque da mesma forma). Você fornece um método de impressão "padrão", mas permite que os usuários criem uma faceta e um código de idioma e imprimam antes da impressão. Um pouco como a maneira como o impulso imprime sua data e hora. Também é possível carregar sua faceta no código do idioma global para imprimir dessa maneira por padrão.
CashCow
2

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_tagpara retornar pretty_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:

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>{}> {};

Uma biblioteca que nos permite visitar o conteúdo de um objeto iterável ou de 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) );
}

Uma bonita biblioteca de impressão:

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 do teste:

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";
}

exemplo ao vivo

Isso usa recursos do C ++ 14 (alguns _taliases e auto&&lambdas), mas nenhum é essencial.

Yakk - Adam Nevraumont
fonte
Versão de trabalho do @KerrekSB, com algumas alterações. A maior parte do código é geral "visite tuplas / iteráveis" e formatação sofisticada (incluindo ->dentro de pairs de maps) 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.
Yakk - Adam Nevraumont
1

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.

Leonid Volnitsky
fonte
Interessante. Gosto da abordagem de modelo de modelo para contêineres, mas funciona para contêineres personalizados e contêineres STL com predicados ou alocadores não padrão? (Fiz algo semelhante para uma tentativa de implementar um bimap em C ++ 0x usando modelos variados.) Além disso, você não parece usar iteradores genericamente para suas rotinas de impressão; por que o uso explícito de um contador i?
21411 Kerrek SB
O que é contêiner com predicados não padrão? O contêiner personalizado que corresponde à assinatura será impresso. Alocadores não padrão não são suportados no momento, mas é fácil de corrigir. Só não preciso disso por enquanto.
Leonid Volnitsky
Não há boas razões para usar o índice em vez de iteradores. Razões históricas. Vou consertar quando tiver tempo.
Leonid Volnitsky
Por "contêiner com predicados não padrão", quero dizer algo como um std::setcom um comparador personalizado ou um mapa não-ordenado com uma igualdade personalizada. Seria muito importante apoiar essas construções.
26411 Kerrek SB
1

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

Jeffrey Faust
fonte
0

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

/*! \file       print.hpp
 *  \brief      Useful functions for work with STL containers. 
 *          
 *  Now it supports generic print for STL containers like: [elem1, elem2, elem3]
 *  Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
 *  map, multimap, unordered_map, array
 *
 *  \author     Skident
 *  \date       02.09.2016
 *  \copyright  Skident Inc.
 */

#pragma once

// check is the C++11 or greater available (special hack for MSVC)
#if (defined(_MSC_VER) && __cplusplus >= 199711L) || __cplusplus >= 201103L
    #define MODERN_CPP_AVAILABLE 1
#endif


#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <list>
#include <map>
#include <cctype>

#ifdef MODERN_CPP_AVAILABLE
    #include <array>
    #include <unordered_set>
    #include <unordered_map>
    #include <forward_list>
#endif


#define dump(value) std::cout << (#value) << ": " << (value) << std::endl

#define BUILD_CONTENT                                                       \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss << *it << elem_separator;                                    \
        }                                                                   \


#define BUILD_MAP_CONTENT                                                   \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss  << it->first                                                \
                << keyval_separator                                         \
                << it->second                                               \
                << elem_separator;                                          \
        }                                                                   \


#define COMPILE_CONTENT                                                     \
        std::string data = ss.str();                                        \
        if (!data.empty() && !elem_separator.empty())                       \
            data = data.substr(0, data.rfind(elem_separator));              \
        std::string result = first_bracket + data + last_bracket;           \
        os << result;                                                       \
        if (needEndl)                                                       \
            os << std::endl;                                                \



////
///
///
/// Template definitions
///
///

//generic template for classes: deque, list, forward_list, vector
#define VECTOR_AND_CO_TEMPLATE                                          \
    template<                                                           \
        template<class T,                                               \
                 class Alloc = std::allocator<T> >                      \
        class Container, class Type, class Alloc>                       \

#define SET_TEMPLATE                                                    \
    template<                                                           \
        template<class T,                                               \
                 class Compare = std::less<T>,                          \
                 class Alloc = std::allocator<T> >                      \
            class Container, class T, class Compare, class Alloc>       \

#define USET_TEMPLATE                                                   \
    template<                                                           \
template < class Key,                                                   \
           class Hash = std::hash<Key>,                                 \
           class Pred = std::equal_to<Key>,                             \
           class Alloc = std::allocator<Key>                            \
           >                                                            \
    class Container, class Key, class Hash, class Pred, class Alloc     \
    >                                                                   \


#define MAP_TEMPLATE                                                    \
    template<                                                           \
        template<class Key,                                             \
                class T,                                                \
                class Compare = std::less<Key>,                         \
                class Alloc = std::allocator<std::pair<const Key,T> >   \
                >                                                       \
        class Container, class Key,                                     \
        class Value/*, class Compare, class Alloc*/>                    \


#define UMAP_TEMPLATE                                                   \
    template<                                                           \
        template<class Key,                                             \
                   class T,                                             \
                   class Hash = std::hash<Key>,                         \
                   class Pred = std::equal_to<Key>,                     \
                   class Alloc = std::allocator<std::pair<const Key,T> >\
                 >                                                      \
        class Container, class Key, class Value,                        \
        class Hash, class Pred, class Alloc                             \
                >                                                       \


#define ARRAY_TEMPLATE                                                  \
    template<                                                           \
        template<class T, std::size_t N>                                \
        class Array, class Type, std::size_t Size>                      \



namespace eos
{
    static const std::string default_elem_separator     = ", ";
    static const std::string default_keyval_separator   = " => ";
    static const std::string default_first_bracket      = "[";
    static const std::string default_last_bracket       = "]";


    //! Prints template Container<T> as in Python
    //! Supported containers: vector, deque, list, set, unordered_set(C++11), forward_list(C++11)
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    template<class Container>
    void print( const Container& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections with one template argument and allocator as in Python.
    //! Supported standard collections: vector, deque, list, forward_list
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    VECTOR_AND_CO_TEMPLATE
    void print( const Container<Type>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Type>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:set<T, Compare, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    SET_TEMPLATE
    void print( const Container<T, Compare, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<T, Compare, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:unordered_set<Key, Hash, Pred, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    USET_TEMPLATE
    void print( const Container<Key, Hash, Pred, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Key, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:map<T, U> as in Python
    //! supports generic objects of std: map, multimap
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    MAP_TEMPLATE
    void print(   const Container<Key, Value>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints classes like std:unordered_map as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    UMAP_TEMPLATE
    void print(   const Container<Key, Value, Hash, Pred, Alloc>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:array<T, Size> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    ARRAY_TEMPLATE
    void print(   const Array<Type, Size>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
            )
    {
        typename Array<Type, Size>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Removes all whitespaces before data in string.
    //! \param str string with data
    //! \return string without whitespaces in left part
    std::string ltrim(const std::string& str);

    //! Removes all whitespaces after data in string
    //! \param str string with data
    //! \return string without whitespaces in right part
    std::string rtrim(const std::string& str);

    //! Removes all whitespaces before and after data in string
    //! \param str string with data
    //! \return string without whitespaces before and after data in string
    std::string trim(const std::string& str);



    ////////////////////////////////////////////////////////////
    ////////////////////////ostream logic//////////////////////
    /// Should be specified for concrete containers
    /// because of another types can be suitable
    /// for templates, for example templates break
    /// the code like this "cout << string("hello") << endl;"
    ////////////////////////////////////////////////////////////



#define PROCESS_VALUE_COLLECTION(os, collection)                            \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

#define PROCESS_KEY_VALUE_COLLECTION(os, collection)                        \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_keyval_separator,                                       \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

    ///< specialization for vector
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::vector<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for deque
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::deque<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for set
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for multiset
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::multiset<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

#ifdef MODERN_CPP_AVAILABLE
    ///< specialization for unordered_map
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::unordered_set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for forward_list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::forward_list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for array
    template<class T, std::size_t N>
    std::ostream& operator<<(std::ostream& os, const std::array<T, N>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }
#endif

    ///< specialization for map, multimap
    MAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for unordered_map
    UMAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }
}
Skident
fonte