프리 프린트 C ++ STL 컨테이너


389

이 게시물 끝에 업데이트 내용을 기록해 두십시오.

업데이트 : 이 라이브러리의 GitHub공개 프로젝트를 만들었습니다 !


를 통해 모든 STL 컨테이너를 예쁘게 인쇄하는 단일 템플릿을 원합니다 operator<<. 의사 코드에서 다음과 같은 것을 찾고 있습니다.

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

이제 나는 여기에서 생각할 수없는 많은 템플릿 마술을 보았습니다. 그래서 누군가가 모든 컨테이너 C와 일치하는 것을 제안 할 수 있는지 궁금합니다. ?

많은 감사합니다!


업데이트 및 솔루션

채널 9 에서이 문제를 다시 제기 한 후 Sven Groot로부터 환상적인 답변을 얻었습니다 .Sven Groot는 약간의 SFINAE 유형 특성과 결합하여 완전히 일반적이며 중첩 가능한 방식으로 문제를 해결하는 것으로 보입니다. 분리 문자는 개별적으로 특수화 될 수 있으며 std :: set에 대한 특수화 예가 포함되며 사용자 정의 분리 문자를 사용하는 예도 포함됩니다.

도우미 "wrap_array ()"를 사용하여 원시 C 배열을 인쇄 할 수 있습니다. 업데이트 : 페어와 튜플을 인쇄 할 수 있습니다. 기본 구분 기호는 둥근 괄호입니다.

enable-if 유형 특성에는 C ++ 0x가 필요하지만 약간 수정하면 C ++ 98 버전으로 만들 수 있습니다. 튜플에는 가변 템플릿이 필요하므로 C ++ 0x가 필요합니다.

Sven에 솔루션을 게시 할 수 있도록 여기에 게시하도록 요청했지만 그 동안 참조를 위해 코드를 직접 게시하고 싶습니다. ( 업데이트 : Sven은 이제 자신의 코드를 아래에 게시하여 받아 들였습니다. 내 코드는 컨테이너 유형 특성을 사용하지만 나에게 효과적이지만 반복자를 제공하는 컨테이너가 아닌 클래스에서는 예기치 않은 동작이 발생할 수 있습니다.)

헤더 (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

사용 예 :

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

개선을위한 추가 아이디어 :

  • std::tuple<...>같은 방식으로에 대한 출력을 구현하는 것입니다 std::pair<S,T>. 업데이트 : 이것은 이제 별도의 질문입니다 ! 업데이트 : 이제 Xeo 덕분에 구현되었습니다!
  • 도우미 클래스가 전역 네임 스페이스로 번지지 않도록 네임 스페이스를 추가하십시오. 끝난
  • 사용자 지정 구분자 클래스 또는 전 처리기 매크로를 쉽게 만들 수 있도록 템플릿 별칭 (또는 이와 유사한 것)을 추가 하시겠습니까?

최근 업데이트:

  • 인쇄 기능에서 간단한 for 루프를 위해 사용자 정의 출력 반복자를 제거했습니다.
  • 모든 구현 세부 사항은 이제 pretty_print네임 스페이스에 있습니다. 글로벌 스트림 연산자와 pretty_print_array랩퍼 만이 글로벌 네임 스페이스에 있습니다.
  • 이름 공간 operator<<이 이제 올바르게 표시 되도록 수정 했습니다 std.

노트:

  • 출력 반복자를 제거하면 std::copy()예쁘게 인쇄 할 수있는 방법이 없습니다 . 이것이 원하는 기능이라면 pretty iterator를 복원 할 수는 있지만 아래 Sven의 코드에는 구현이 있습니다.
  • 구분 기호를 객체 상수가 아닌 컴파일 타임 상수로 만드는 것은 의도적 인 디자인 결정이었습니다. 즉, 런타임에 구분 기호를 동적으로 제공 할 수 없지만 불필요한 오버 헤드가 없음을 의미합니다. Dennis Zickefoose는 아래의 Sven 코드에 대한 주석에서 객체 기반 구분 기호 구성을 제안했습니다. 원하는 경우 이는 대체 기능으로 구현 될 수 있습니다.
  • 중첩 컨테이너 구분 기호를 사용자 정의하는 방법은 현재 명확하지 않습니다.
  • 이 라이브러리의 목적은 사용자가 코딩 할 필요가없는 빠른 컨테이너 인쇄 기능 을 허용 하는 것입니다. 다목적 포맷 라이브러리가 아니라 컨테이너 검사를 위해 보일러 플레이트 코드를 작성할 필요성을 완화하기위한 개발 도구입니다.

기여한 모든 사람에게 감사합니다!


참고 : 사용자 지정 구분 기호를 빠르게 배포 할 수있는 방법을 찾고 있다면 유형 삭제를 사용하는 방법이 있습니다. 우리는 이미 MyDel다음과 같이 구분자 클래스를 구성했다고 가정합니다 .

struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };

이제 우리는 그 구분 기호를 사용하여 std::cout << MyPrinter(v) << std::endl;일부 컨테이너 에 쓸 수 있기를 원합니다 v. MyPrinter다음과 같이 유형 지우기 클래스가됩니다.

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

코드가 작동하지 않습니다. 컨테이너 C와 같은 키워드는 없습니다
the_drow

31
@the_drow : OP가 이미 알고있는 것 같습니다. 그들은 단지 그들이 찾고있는 것을 나타냅니다.
Marcelo Cantos

실제로, 나는 단지 "도덕적 인"의사 코드 예제를 주었다. (반환 유형도 생략했습니다.) 확실하게 구분 기호를 변경하는 가장 좋은 방법조차 모릅니다.
Kerrek SB

1
다른 대안은 연산자를 pretty_print네임 스페이스에 배치하고 사용자가 인쇄 할 때 사용할 래퍼를 제공하는 것입니다. 사용자 관점에서 : std::cout << pretty_print(v);(아마도 다른 이름으로). 그런 다음 래퍼와 동일한 네임 스페이스에 연산자를 제공 한 다음 원하는대로 인쇄 할 수 있습니다. 또한 전체 응용 프로그램에 동일한 선택을 강제하는 특성을 사용하지 않고 각 호출에서 사용할 구분 기호를 선택적으로 정의 할 수 있도록 래퍼를 향상시킬 수도 있습니다. \
David Rodríguez-dribeas

1
허풍스러운 질문 대신 "업데이트"답변을 실제 답변으로 만드십시오.
einpoklum

답변:


82

이 솔루션은 몇 가지 변경 사항이있는 Marcelo의 솔루션에서 영감을 얻었습니다.

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

Marcelo의 버전과 마찬가지로 지원되는 모든 컨테이너에 특화되어야하는 is_container 유형 특성을 사용합니다. 그것은 확인하는 특성을 사용하는 것이 가능하다 value_type, const_iterator, begin()/ end(),하지만 같이, 그 기준과 일치하지만, 실제로는 용기없는 일을 일치 할 수 있기 때문에 나는 확실히 내가 그 권하고 싶습니다 아니에요 std::basic_string. 또한 Marcelo 버전과 마찬가지로 사용할 구분 기호를 지정하기 위해 특수화 할 수있는 템플릿을 사용합니다.

가장 큰 차이점은에 대해 내 버전을 빌드했다는 것입니다.이 버전 pretty_ostream_iterator은와 비슷 std::ostream_iterator하지만 마지막 항목 다음에 구분 기호를 인쇄하지 않습니다. 컨테이너 포맷은 print_container_helperis_container 특성없이 컨테이너를 인쇄하거나 다른 구분 기호 유형을 지정하는 데 직접 사용할 수있는 로 수행됩니다 .

또한 is_container 및 delimiters를 정의 했으므로 비표준 술어 또는 할당자가있는 컨테이너와 char 및 wchar_t 모두에서 작동합니다. operator << 함수 자체도 char 및 wchar_t 스트림 모두와 작동하도록 정의되어 있습니다.

마지막으로 std::enable_ifC ++ 0x의 일부로 사용할 수 있으며 Visual C ++ 2010 및 g ++ 4.3 (-std = c ++ 0x 플래그 필요) 이상에서 작동하는을 사용했습니다. 이런 식으로 Boost에 의존하지 않습니다.


이 권리를 읽고 있다면 <i, j>한 함수에서와 [i j]다른 함수에서와 같이 페어 프린트를 하려면 소수의 정적 멤버를 사용하여 완전히 새로운 유형을 정의해야합니다 print_container_helper. 지나치게 복잡해 보입니다. 사례별로 설정할 수있는 필드와 다른 기본값을 제공하는 특수화를 사용하여 실제 객체를 사용하는 것이 어떻습니까?
Dennis Zickefoose

이런 식으로보십시오 : 개인적으로 좋아하는 많은 구분자가있는 경우 정적 멤버로 한 번에 두 개의 클래스를 만든 다음 사용할 수 있습니다. 물론 사용 print_container_helper하는 것만으로 우아하지는 않습니다 operator<<. 물론 소스를 항상 변경하거나 좋아하는 컨테이너에 대한 명시 적 전문화를 추가 할 수 있습니다 (예 : for pair<int, int>및 for) pair<double, string>. 궁극적으로 그것은 편의성과 비교하여 전력을 계량하는 문제입니다. 개선을위한 제안 환영합니다!
Kerrek SB 2016 년

... 따라서 다른 형식으로 동일한 데이터 유형 의 상황 인쇄가 필요한 경우 어쨌든 적어도 하나의 작은 래퍼를 작성해야 할 것입니다. 이것은 고도로 구성 가능한 형식 라이브러리,하지만 마술 당신이 ... 생각없이 컨테이너를 인쇄 할 수 있습니다 (하지만 당신은 더 원하는 경우 오히려 제로 노력 현열 기본 라이브러리가 아닌 글로벌 유연성을, 우리는 아마 메이크업에 일부 #macros를 추가 할 수 있습니다 기본값은 조작하기 쉽다.)
Kerrek SB

실제 문제는 사용자 정의 구분 기호에 매개 변수를 사용하기 위해 print_container_helper를 쉽게 수정할 수 있지만 구분 기호 템플릿을 전문화하는 것 외에 내부 컨테이너 (또는 쌍)에 구분 기호를 지정하는 방법은 실제로는 없다는 것입니다. 그것을 달성하는 것은 매우 복잡합니다.
Sven

나는 유형 삭제를 사용하여 편리한 사용자 정의 구분 기호 솔루션을 거의 관리하고 있습니다. 구분자 클래스가 이미 있다면 MyDels말할 수 있습니다 std::cout << CustomPrinter<MyDels>(x);. 내가 할 수없는 순간에하는 일은 말 것입니다 std::cout << CustomDelims<"{", ":", "}">(x);당신이 할 수 없기 때문에, const char *템플릿 인수를. 구분 기호를 컴파일 타임을 일정하게 유지하기로 결정하면 사용 편의성을 약간 제한하지만 가치가 있다고 생각합니다.
Kerrek SB

22

이것은 몇 번 편집되었으며 컬렉션 RangePrinter를 래핑하는 기본 클래스를 호출하기로 결정했습니다.

한 번의 연산자 << 과부하를 작성한 후에는 컬렉션에 자동으로 작동해야합니다. 단, 맵에 쌍을 인쇄하기 위해 특수한 연산자가 필요하고 구분 기호를 사용자 지정할 수도 있습니다.

항목을 직접 출력하는 대신 항목에 사용할 특수한 "인쇄"기능을 사용할 수도 있습니다. STL 알고리즘과 같은 비트를 사용하면 사용자 정의 술어를 전달할 수 있습니다. map을 사용하면 std :: pair 용 사용자 정의 프린터와 함께이 방법을 사용합니다.

"기본"프린터는 프린터를 스트림으로 출력합니다.

자, 커스텀 프린터로 작업합시다. 외부 클래스를 RangePrinter로 변경하겠습니다. 따라서 2 개의 반복자와 일부 구분자가 있지만 실제 항목을 인쇄하는 방법을 사용자 정의하지 않았습니다.

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

이제 기본적으로 키 및 값 유형이 인쇄 가능하고 (다른 유형과 마찬가지로) 또는 원하지 않는 경우를 위해 자신의 특수 항목 프린터에 넣을 수있는 한 맵에서 작동합니다. = 구분 기호로 사용하십시오.

자유 기능을 움직여서 끝까지 만들었습니다.

자유 기능 (반복자 버전)은 다음과 같으며 기본값을 가질 수도 있습니다.

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

그런 다음 std :: set by

 std::cout << outputFormatter( mySet );

사용자 정의 프린터를 사용하는 자유 기능 버전과 두 개의 반복자를 사용하는 자유 버전을 작성할 수도 있습니다. 어쨌든 그들은 당신을 위해 템플릿 매개 변수를 해결하고 임시로 전달할 수 있습니다.


내가 참조. 이것은 Marcelo Cantos의 아이디어와 비슷합니까? 나는 이것을 실례로 바꾸려고 노력할 것이다. 감사합니다!
Kerrek SB

이 솔루션은 Marcelo보다 훨씬 깨끗하며 동일한 유연성을 제공합니다. 출력을 함수 호출로 명시 적으로 래핑 해야하는 측면이 마음에 듭니다. 정말 멋지려면 다양한 반복자를 직접 출력하는 지원을 추가하여 할 수 있습니다 std::cout << outputFormatter(beginOfRange, endOfRange);.
Björn Pollex

1
@CashCow :이 솔루션에는 한 가지 문제가 있지만 재귀 컬렉션 (예 : 컬렉션 컬렉션)에서는 작동하지 않는 것 같습니다. std::pair"내부 컬렉션"의 가장 기본적인 예입니다.
Matthieu M.

의존성이 없으며 지원하는 컨테이너에 대해 알 필요가 없으므로이 답변을 매우 좋아합니다. 우리가 std::map쉽게 처리 할 수 ​​있는지 , 그리고 컬렉션 컬렉션에 작동하는지 알아낼 수 있습니까 ? 그래도 이것을 대답으로 받아들이고 싶습니다. Marcelo가 신경 쓰지 않기를 바랍니다. 그의 솔루션도 효과가 있습니다.
Kerrek SB

@Matthieu M. 내부 컬렉션을 인쇄하는 방법에 따라 다릅니다. os << open << * iter << close 만 사용하면 문제가 발생하지만 사용자가 제안한대로 사용자 정의 프린터를 전달하도록 허용하면 원하는 것을 인쇄 할 수 있습니다.
CashCow

14

다음은 방금 해킹 한 완전한 작업 프로그램으로 제공되는 작업 라이브러리입니다.

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

현재 vector및 로만 작동 set하지만 IsContainer전문화 를 확장하여 대부분의 컨테이너에서 작동하도록 만들 수 있습니다 . 이 코드가 최소한인지에 대해서는 많이 생각하지 않았지만 중복으로 제거 할 수있는 것을 즉시 생각할 수는 없습니다.

편집 : 차기 위해 배열을 처리하는 버전을 포함 시켰습니다. 추가 모호성을 피하기 위해 char 배열을 제외해야했습니다. 여전히 문제가 발생할 수 있습니다 wchar_t[].


2
@ Nawaz : 내가 말했듯이, 이것은 해결책의 시작일뿐입니다. std::map<>연산자를 전문화하거나 operator<<for 를 정의하여 지원할 수 있습니다 std::pair<>.
Marcelo Cantos

그러나 Delims클래스 템플릿 을 사용하는 경우 +1 !
Nawaz

@MC : 오 좋아요. 이것은 매우 유망 해 보입니다! (그런데 리턴 타입 "std :: ostream &"이 필요합니다. 처음에는 잊어 버렸습니다.)
Kerrek SB

흠, std :: vector <int> 및 std :: set <std :: string>에서 이것을 시도 할 때 "모호한 과부하"가 발생합니다.
Kerrek SB

그렇습니다. 현재 operator<<템플릿이 어떤 것과도 일치 한다는 사실로 인해 모호성을 방지하는 방법을 알아 냈습니다 .
Marcelo Cantos

10

{fmt} 라이브러리를 사용하여 컨테이너와 범위 및 튜플의 형식을 지정할 수 있습니다 . 예를 들면 다음과 같습니다.

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

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

인쇄물

{1, 2, 3}

stdout.

면책 조항 : 저는 {fmt}의 저자입니다.


8

이 코드는 현재 여러 경우에 편리하다는 것이 입증되었으며 사용법이 매우 낮기 때문에 커스터마이징에 드는 비용이 있다고 생각합니다. 따라서 MIT 라이센스 하에 릴리스 하고 헤더와 작은 예제 파일을 다운로드 할 수있는 GitHub 리포지토리를 제공 하기로 결정했습니다 .

http://djmuw.github.io/prettycc

0. 서문과 표현

이 답변 의 '장식' 은 접두사 문자열, 구분자 문자열 및 접미사 문자열의 집합입니다. 접두사 문자열이 컨테이너 값 앞과 뒤에 접두사 문자열에 삽입되는 위치 (2. 대상 컨테이너 참조) 구분 기호 문자열은 각 컨테이너의 값 사이에 삽입됩니다.

참고 : 실제로이 답변은 사용자 정의 장식이 현재 스트림에 적용되었는지 여부를 확인하기 위해 런타임 검사가 필요하기 때문에 장식이 엄격하게 컴파일 된 시간 상수가 아니기 때문에 질문을 100 %로 처리하지 않습니다. 그럼에도 불구하고, 괜찮은 기능이 있다고 생각합니다.

참고 2 : 아직 제대로 테스트되지 않았기 때문에 사소한 버그가있을 수 있습니다.

1. 일반적인 아이디어 / 사용

사용에 필요한 추가 코드가 없습니다.

그것은 쉽게 유지되어야한다

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

손쉬운 커스터마이징 ...

... 특정 스트림 객체와 관련하여

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

또는 모든 스트림과 관련하여 :

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

대략적인 설명

  • 이 코드에는 모든 유형의 기본 장식을 제공하는 클래스 템플릿이 포함되어 있습니다.
  • (a) 특정 유형의 기본 장식을 변경하도록 전문화 할 수 있으며
  • 특정 스트림에서 특정 유형을 장식하는 객체에 대한 포인터를 저장하기 위해 / 를 ios_base사용하여 제공된 개인 저장소 사용xallocpwordpretty::decor

pretty::decor<T>이 스트림의 오브젝트가 명시 적으로 설정되어 있지 않은 경우 pretty::defaulted<T, charT, chartraitT>::decoration(), 지정된 형태의 디폴트 장식을 취득하기 위해서 불려갑니다. 클래스pretty::defaulted 는 기본 장식을 사용자 정의하도록 전문화되어야합니다.

2. 대상 물체 / 용기

이 코드 obj'예쁜 장식' 을 위한 대상 객체 는

  • 과부하 std::beginstd::end 정의 (C- 스타일 배열 포함)
  • 가진 begin(obj)end(obj)ADL을 통해 사용할 수,
  • 유형이다 std::tuple
  • 또는 유형 std::pair입니다.

이 코드에는 범위 기능 ( begin/ end) 이있는 클래스를 식별하는 특성이 포함되어 있습니다. (확인 여부는 포함되어 있지 않습니다begin(obj) == end(obj) 그러나 유효한 표현식 는 .)

이 코드는 operator<<전역 네임 스페이스에 s를 제공하며 ,보다 특수화 된 버전을 operator<<사용할 수 없는 클래스에만 적용됩니다 . 따라서 예 std::string를 들어이 코드에서 연산자를 사용하여 유효한 begin/end 쌍을 .

3. 활용 및 사용자 정의

장식은 모든 유형 (다른 유형 제외 tuple)과 스트림 (스트림 유형이 아님)에 대해 별도로 부과 될 수 있습니다 . (즉std::vector<int> 스트림 개체마다 장식이 다를 수 있습니다.)

A) 기본 장식

기본 접두사는 ""기본 접미사와 같이 (없음)이며 기본 구분 기호는 ", "(쉼표 + 공백)입니다.

B) pretty::defaulted클래스 템플릿 을 전문화하여 유형의 맞춤형 기본 장식

struct defaulted고정 부재의 기능을 갖는 decoration()반환을decor 주어진 타입에 대한 디폴트 값을 포함하는 개체.

배열을 사용하는 예 :

기본 배열 인쇄를 사용자 정의하십시오.

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

잘못된 배열을 인쇄하십시오.

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)

PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)매크로 사용char스트림에

매크로가 확장됩니다

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

위의 부분 전문화를

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

또는 전체 전문화를 삽입

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

wchar_t스트림에 대한 다른 매크로 가 포함되어 PRETTY_DEFAULT_WDECORATION있습니다..

C) 개울에 장식을 부과

이 기능 pretty::decoration은 특정 스트림에 장식을 부과하는 데 사용됩니다. 구분 기호 인 하나의 문자열 인수 (기본 클래스에서 접두사 및 접미사 채택) 또는 전체 장식을 조립하는 세 개의 문자열 인수를 사용하는 과부하가 있습니다.

주어진 유형과 시내를위한 완벽한 장식

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

주어진 스트림에 대한 구분 기호 사용자 정의

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. 특별 취급 std::tuple

가능한 모든 튜플 유형에 대한 전문화를 허용하는 대신이 코드는 std::tuple<void*>모든 종류의 장식에 적용 할 수있는 모든 장식을 적용합니다 std::tuple<...>.

5. 스트림에서 맞춤 장식 제거

주어진 유형의 기본 장식으로 돌아가려면 pretty::clear스트림에서 함수 템플릿을 사용하십시오 s.

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

5. 추가 예

줄 바꿈 문자로 "매트릭스 유사"인쇄

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;

인쇄물

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

에서 볼 ideone / KKUebZ

6. 코드

#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_

4

이전 답변과 다른 접근법을 생각해 냈으므로 로케일 패싯을 사용하기 때문에 여기에 다른 답변을 추가하려고합니다.

기본은 여기

본질적으로 당신이하는 일은 :

  1. 에서 파생되는 클래스를 만듭니다 std::locale::facet. 약간의 단점은 ID를 보유 할 어딘가에 컴파일 유닛이 필요하다는 것입니다. MyPrettyVectorPrinter라고하겠습니다. 더 나은 이름을 지정하고 쌍과지도를위한 이름을 만들 수도 있습니다.
  2. 스트림 기능에서 확인 std::has_facet< MyPrettyVectorPrinter >
  3. 그것이 true를 반환하면 std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. 패싯 오브젝트에는 분리 문자에 대한 값이 있으며 읽을 수 있습니다. 패싯을 찾을 수 없으면 인쇄 기능 ( operator<<)이 기본 패싯을 제공합니다. 벡터를 읽을 때도 같은 작업을 수행 할 수 있습니다.

이 방법은 기본 인쇄를 사용하면서도 사용자 지정 재정의를 계속 사용할 수 있기 때문에이 방법이 좋습니다.

단점은 여러 프로젝트에서 사용되는 경우 패싯에 대한 라이브러리가 필요하므로 헤더 전용이 될 수 없으며 새 로케일 객체를 만드는 데 드는 비용에 대해주의해야한다는 사실입니다.

나는 두 가지 접근 방식이 모두 정확하고 선택을 할 수 있다고 생각하기 때문에 다른 솔루션을 수정하는 대신 새로운 솔루션으로 작성했습니다.


바로이 방법을 사용하겠습니다.이 방법을 사용하려면 사용하려는 각 컨테이너 유형을 적극적으로 허용해야합니까?
Kerrek SB

글쎄 실제로 하나는 자신의 유형 이외의 std를 확장해서는 안되지만 각 컨테이너 유형 (벡터, 맵, 목록, deque)에 인쇄 할 수있는 쌍에 operator <<의 과부하를 씁니다. 물론 일부는 패싯을 공유 할 수 있습니다 (예 : 목록, 벡터를 인쇄하고 모두 동일하게 deque 할 수 있음). "기본"인쇄 방법을 제공하지만 사용자가 인쇄하기 전에 패싯과 로케일 및 imbue를 작성할 수 있습니다. boost는 date_time을 인쇄하는 방식과 비슷합니다. 패싯을 전역 로케일에로드하여 기본적으로 해당 방식으로 인쇄 할 수도 있습니다.
CashCow

2

여기서 목표는 ADL을 사용하여 예쁘게 인쇄하는 방법을 사용자 정의하는 것입니다.

포맷터 태그를 전달하고 태그의 네임 스페이스에서 4 개의 함수 (이전, 이후, 사이 및 내림차순)를 재정의합니다. 이는 컨테이너를 반복 할 때 포맷터가 '장식'을 인쇄하는 방식을 변경합니다.

{(a->b),(c->d)}맵, (a,b,c)튜플 로이드, "hello"문자열, [x,y,z]기타 모든 것을 포함 하는 기본 포맷터입니다 .

써드 파티 iterable 유형과 함께 "그냥 작동"해야합니다 (및 "다른 모든 것"으로 취급).

타사 iterables에 대한 사용자 지정 장식품을 원한다면 자신 만의 태그를 만드십시오. 지도 하강을 처리하는 데 약간의 작업이 필요합니다 ( pretty_print_descend( your_tag를 반환 하려면 과부하 가 필요합니다 pretty_print::decorator::map_magic_tag<your_tag>). 확실하지 않은 방법으로이 작업을 수행 할 수 있습니다.

반복성과 튜플을 감지하는 작은 라이브러리 :

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

iterable 또는 tuple 유형 객체의 내용을 방문 할 수있는 라이브러리 :

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

예쁜 인쇄 라이브러리 :

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

테스트 코드 :

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

라이브 예

이것은 C ++ 14 기능 (일부 _t별칭 및 auto&&람다)을 사용하지만 필수 기능은 없습니다.


@KerrekSB 작업 버전, 일부 변경. 코드의 대부분은 일반적으로 "방문 튜플 / 반복 가능 객체", 그리고 화려한 서식 (포함입니다 ->내에서 pair의의 map이 시점에서의). 예쁜 인쇄 라이브러리의 핵심은 훌륭하고 작습니다. 성공했는지 확실하지 않고 쉽게 확장 할 수 있도록 노력했습니다.
Yakk-Adam Nevraumont

1

내 솔루션은 simple.h 입니다. scc 패키지의 입니다. 모든 표준 컨테이너, 맵, 세트, ​​c- 어레이를 인쇄 할 수 있습니다.


흥미 롭군 컨테이너에 대한 템플릿 템플릿 접근 방식이 마음에 들지만 비표준 술어 또는 할당자가있는 사용자 정의 컨테이너 및 STL 컨테이너에서 작동합니까? ( Variadic 템플릿을 사용하여 C ++ 0x에서 bimap구현 하려는 시도와 비슷한 작업을 수행했습니다 .) 또한 인쇄 루틴에 일반적으로 반복자를 사용하지 않는 것 같습니다. 왜 카운터를 명시 적으로 사용 i합니까?
Kerrek SB 2016

비표준 술어가있는 컨테이너는 무엇입니까? 서명과 일치하는 사용자 정의 컨테이너가 인쇄됩니다. 비표준 할당자는 현재 지원되지 않지만 쉽게 수정할 수 있습니다. 난 그냥 지금이 필요하지 않습니다.
Leonid Volnitsky 2016 년

반복자 대신 인덱스를 사용해야 할 이유가 없습니다. 역사적 이유. 내가 시간이있을 때 그것을 고칠 것입니다.
Leonid Volnitsky 2014 년

"비표준 술어를 가진 컨테이너"란 std::set사용자 정의 비교기를 사용하거나 사용자 정의 동등성을 갖는 unorder_map과 같은 것을 의미합니다 . 이러한 구조를 지원하는 것이 매우 중요합니다.
Kerrek SB 2016

1

첫 번째 BoostCon (현재 CppCon) 중 하나에서 나온 나와 다른 두 사람이 라이브러리에서이 작업을 수행했습니다. 주요 문제점은 네임 스페이스 std를 확장해야했습니다. 그것은 부스트 ​​라이브러리에 대한 이동으로 밝혀졌습니다.

불행히도 코드 링크는 더 이상 작동하지 않지만 토론에서 흥미로운 내용을 찾을 수 있습니다 (적어도 이름을 말하지 않는 것)!

http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html


0

다음은 2016 년에 수행 된 구현 버전입니다.

하나의 헤더에 모든 것이 있으므로 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;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.