use - what is a vector in c++




Recipientes C++ STL Pretty-print (5)

Por favor, tome nota das atualizações no final deste post.

Atualização: Eu criei um projeto público no GitHub para esta biblioteca!

Eu gostaria de ter um único modelo que, de uma vez por todas, cuida da impressão bonita de todos os contêineres STL via operator<< . No pseudo código, estou procurando algo como 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 vi muita magia de modelo aqui no SO que nunca pensei ser possível, então estou pensando se alguém pode sugerir algo que combine com todos os containers C. Talvez algo característico que possa descobrir se algo tem o iterador necessário ?

Muito Obrigado!

Atualizar (e solução)

Depois de levantar este problema novamente no Channel 9 , recebi uma resposta fantástica de Sven Groot, que, combinada com um pouco de traço do tipo SFINAE, parece resolver o problema de uma forma completamente geral e encaixável. Os delimitadores podem ser especializados individualmente, um exemplo de especialização para std :: set é incluído, bem como um exemplo de uso de delimitadores personalizados.

O auxiliar "wrap_array ()" pode ser usado para imprimir arrays C brutos. Atualização: Pares e tuplas estão disponíveis para impressão; Delimitadores padrão são parênteses.

O traço do tipo enable-if requer o C ++ 0x, mas com algumas modificações deve ser possível fazer uma versão C ++ 98 disso. Tuplas requerem modelos variadic, portanto, C ++ 0x.

Eu pedi a Sven para postar a solução aqui para que eu possa aceitar, mas enquanto isso eu gostaria de postar o código para referência. ( Atualização: Sven agora postou 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 um comportamento inesperado com classes não-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
  ;
}

Outras idéias para melhorias:

  • Implemente a saída para std::tuple<...> da mesma forma que temos para std::pair<S,T> . Update: Esta é agora uma questão separada sobre SO ! Upupdate: Isso agora foi implementado, graças ao Xeo!
  • Adicione namespaces para que as classes auxiliares não sangrem no namespace global. Feito
  • Adicionar alias de modelo (ou algo semelhante) para facilitar a criação de classes de delimitadores 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 namespace pretty_print . Somente os operadores de fluxo global e o wrapper pretty_print_array estão no namespace global.
  • Corrigido o namespace de modo que o operator<< esteja agora corretamente em std .

Notas:

  • Remover o 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 este é um recurso desejado, mas o código de Sven abaixo tem a implementação.
  • Foi uma decisão de design consciente fazer as constantes de tempo de compilação dos delimitadores em vez de constantes de objeto. 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 baseada em objeto foi proposta por Dennis Zickefoose em um comentário ao código de Sven abaixo. Se desejado, isso poderia ser implementado como um recurso alternativo.
  • Atualmente, não é óbvio como personalizar os delimitadores de contêiner aninhados.
  • Lembre-se de que o propósito desta biblioteca é permitir instalações rápidas de impressão de contêineres que exijam codificação zero de sua parte. Não é uma biblioteca de formatação para todos os fins, mas sim uma ferramenta de desenvolvimento para aliviar a necessidade de escrever código de placa de caldeira para inspeção de contêiner.

Obrigado a todos que contribuíram!

Nota: Se você estiver procurando uma maneira rápida de implantar delimitadores personalizados, aqui está uma maneira de usar o apagamento de tipos. Nós 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 ser capazes de escrever std::cout << MyPrinter(v) << std::endl; para algum contêiner v usando esses delimitadores. MyPrinter será uma classe de apagamento de tipos, da seguinte forma:

struct wrapper_base
{
  virtual ~wrapper_base() { }
  virtual std::ostream & stream(std::ostream & o) = 0;
};

template <typename T, typename Delims>
struct wrapper : public wrapper_base
{
  wrapper(const T & t) : t(t) { }
  std::ostream & stream(std::ostream & o)
  {
    return o << pretty_print::print_container_helper<T, char, std::char_traits<char>, Delims>(t);
  }
private:
  const T & t;
};

template <typename Delims>
struct MyPrinter
{
  template <typename Container> MyPrinter(const Container & c) : base(new wrapper<Container, Delims>(c)) { }
  ~MyPrinter() { delete base; }
  wrapper_base * base;
};

template <typename Delims>
std::ostream & operator<<(std::ostream & o, const MyPrinter<Delims> & p) { return p.base->stream(o); }

Esta solução foi inspirada na solução de Marcelo, com algumas mudanças:

#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 um traço do tipo is_container que deve ser especializado para todos os contêineres a serem suportados. Pode ser possível usar um trait para verificar value_type , const_iterator , begin() / end() , mas não tenho certeza se recomendaria que, uma vez que pode corresponder a itens que correspondem a esses critérios, mas que na verdade não são contêineres, 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í a minha versão em torno de um pretty_ostream_iterator , que funciona de forma semelhante ao std::ostream_iterator mas não imprime um delimitador após o último item. A formatação dos contêineres é feita pelo print_container_helper , que pode ser usado diretamente para imprimir contêineres sem um atributo is_container ou para especificar um tipo de delimitadores diferente.

Também defini is_container e delimitators para que funcionem 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 trabalhar com fluxos char e wchar_t.

Finalmente, usei std::enable_if , que está disponível como parte do C ++ 0x, e funciona no Visual C ++ 2010 e no g ++ 4.3 (precisa do sinalizador -std = c ++ 0x) e posterior. Desta forma, não há dependência no Boost.


Isso foi editado algumas vezes e decidimos chamar a classe principal que envolve uma coleção RangePrinter

Isso deve funcionar automaticamente com qualquer coleção, uma vez que você tenha escrito a sobrecarga do operador de uma só vez, exceto que você precisará de uma especial para os mapas para imprimir o par, e pode querer customizar o delimitador lá.

Você também pode ter uma função especial "print" para usar no item, em vez de simplesmente fazer a saída direta. Um pouco como os algoritmos STL permitem passar predicados personalizados. Com o mapa você usaria desta forma, com uma impressora personalizada para o std :: pair.

Sua impressora "padrão" apenas enviaria para o fluxo.

Ok, vamos trabalhar em uma impressora personalizada. Eu vou mudar minha classe externa para RangePrinter. Portanto, temos dois 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 sua própria impressora de item especial para quando eles não forem (como você pode com qualquer outro tipo), ou se você não quiser = como o delimitador.

Estou movendo a função livre para criá-las até o fim agora:

Uma função livre (versão do iterador) se pareceria com algo assim e você poderia 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 então usá-lo para std :: set by

 std::cout << outputFormatter( mySet );

Você também pode escrever uma versão de função livre que usa uma impressora personalizada e outras que usam dois iteradores. Em qualquer caso, eles resolverão os parâmetros do modelo para você e você poderá passá-los como temporários.


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 funciona apenas com vectore set, mas pode ser feito para trabalhar com a maioria dos contêineres, apenas expandindo as IsContainerespecializações. Eu não tenho pensado muito sobre se esse código é mínimo, mas não posso pensar imediatamente em algo que eu possa excluir como redundante.

EDIT: Apenas por diversão, eu incluí uma versão que lida com matrizes. Eu tive que excluir matrizes de caracteres para evitar mais ambigüidades; pode ainda ter problemas com isso wchar_t[].


Eu vou adicionar outra resposta aqui, porque eu tenho uma abordagem diferente da minha anterior, que é usar facetas de localidade.

O básico está here

Essencialmente o que você faz é:

  1. Crie uma classe que deriva std::locale::facet. A ligeira 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 um para par e mapa.
  2. Na sua função de fluxo, você verifica std::has_facet< MyPrettyVectorPrinter >
  3. Se isso retornar true, 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á os padrões. Note que você pode fazer a mesma coisa para ler um vetor.

Eu gosto deste método porque você pode usar uma impressão padrão enquanto ainda é capaz de usar uma substituição personalizada.

As desvantagens estão precisando de uma biblioteca para sua faceta se usadas em vários projetos (portanto, não podem ser apenas cabeçalhos) e também o fato de que você precisa tomar cuidado com as despesas de criar um novo objeto de localidade.

Escrevi isso como uma nova solução, em vez de modificar minha outra, porque acredito que ambas as abordagens podem estar corretas e você escolhe.


Minha solução é simple.h , que faz parte do pacote scc . Todos os contêineres std, mapas, conjuntos, arrays c são imprimíveis.







pretty-print