漂亮的C ++ STL容器

问题描述 投票:367回答:9

请注意本文末尾的更新。

更新:我为这个库创建了一个public project on GitHub


我希望有一个模板,一劳永逸地通过operator<<来打印所有STL容器。在伪代码中,我正在寻找这样的东西:

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

现在我在SO上看到了很多模板魔法,这是我从未想过的可能,所以我想知道是否有人可以建议一些能匹配所有容器的东西C.也许某些特性可以判断出某些东西是否有必要的迭代器?

非常感谢!


更新(和解决方案)

Channel 9上再次提出这个问题之后,我从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>实现输出。 更新:现在这是一个separate question on SO! Upupdate:由于Xeo,现在已经实现了!
  • 添加名称空间,以便辅助类不会渗透到全局名称空间中。 完成
  • 添加模板别名(或类似的东西)以便于制作自定义分隔符类,或者可能是预处理器宏?

最近更新:

  • 我删除了自定义输出迭代器,转而使用print函数中的简单for循环。
  • 所有实现细节现在都在pretty_print命名空间中。只有全局流运算符和pretty_print_array包装器位于全局命名空间中。
  • 修复了命名空间,以便operator<<现在正确地在std中。

笔记:

  • 删除输出迭代器意味着无法使用std::copy()进行漂亮的打印。如果这是一个理想的功能,我可能会恢复漂亮的迭代器,但下面的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;编写vMyPrinter将是一个类型擦除类,如下所示:

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++ templates c++11 operator-overloading pretty-print
9个回答
80
投票

这个解决方案的灵感来自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_typeconst_iteratorbegin() / end(),但我不确定我会推荐它,因为它可能匹配符合这些标准但实际上不是容器的东西,如std::basic_string。与Marcelo的版本一样,它使用专门用于指定要使用的分隔符的模板。

主要区别在于我已经围绕pretty_ostream_iterator构建了我的版本,std::ostream_iteratorprint_container_helper类似,但在最后一项之后不会打印分隔符。格式化容器由std::enable_if完成,它可以直接用于打印没有is_container特征的容器,或者指定不同的分隔符类型。

我还定义了is_container和delimiters,因此它适用于具有非标准谓词或分配器的容器,以及char和wchar_t。 operator <<函数本身也被定义为使用char和wchar_t流。

最后,我使用了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; } ,它可以作为C ++ 0x的一部分使用,并且可以在Visual C ++ 2010和g ++ 4.3(需要-std = c ++ 0x标志)和更高版本中使用。这种方式不依赖于Boost。


21
投票

这已被编辑了几次,我们决定调用包装集合RangePrinter的主类

一旦您编写了一次性操作符<< overload,这将自动与任何集合一起工作,除了您需要一个特殊的地图来打印该对,并且可能想要在那里自定义分隔符。

您还可以在项目上使用特殊的“打印”功能,而不是直接输出。有点像STL算法允许您传递自定义谓词。使用map,您可以使用这种方式,使用std :: pair的自定义打印机。

您的“默认”打印机只会将其输出到流。

好吧,让我们使用自定义打印机。我将我的外部类更改为RangePrinter。所以我们有2个迭代器和一些分隔符,但没有自定义如何打印实际项目。

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::cout << outputFormatter( mySet );

然后你可以将它用于std :: set by

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

您还可以编写采用自定义打印机的自由功能版本和采用两个迭代器的自由功能版本。在任何情况下,他们都会为您解析模板参数,您将能够将它们作为临时文件传递给您。


15
投票

这是一个工作库,作为一个完整的工作程序呈现,我刚刚一起攻击:

vector

它目前只适用于setIsContainer,但只需扩展wchar_t[]专业,就可以与大多数容器一起使用。我没有想过这个代码是否是最小的,但我不能立即想到任何我可以删除的冗余。

编辑:只是为了踢,我包括一个处理数组的版本。我不得不排除char数组以避免进一步的模糊;它可能仍然与http://djmuw.github.io/prettycc陷入困境。


6
投票

现在,这些代码在几个方面被证明是方便的,我觉得进入自定义的费用很低。因此,我决定在MIT许可下发布它,并提供一个GitHub存储库,其中可以下载标题和一个小示例文件。

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

0. Preface and wording

就这个答案而言,“装饰”是一组前缀字符串,分隔符字符串和后缀字符串。前缀字符串插入到流之前和后缀字符串之后的容器值(请参阅2.目标容器)。分隔符字符串插入相应容器的值之间。

注意:实际上,这个答案没有解决100%的问题,因为装饰不是严格编译的时间常数,因为需要运行时检查来检查自定义装饰是否已应用于当前流。不过,我认为它有一些不错的功能。

注2:可能有小错误,因为它尚未经过良好测试。

1. General idea/usage

Zero additional code required for usage

它应该像保持一样简单

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

Easy customization ...

...关于特定流对象

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

或关于所有流:

ios_base

Rough description

  • 该代码包括一个类模板,为任何类型提供默认装饰
  • 它可以专门用于更改(a)某些类型的默认装饰,它是
  • 使用xalloc使用pword / pretty::decor提供的私有存储,以保存指向pretty::decor<T>对象的指针,该对象专门装饰某个流上的某种类型。

如果没有明确设置此流的pretty::defaulted<T, charT, chartraitT>::decoration()对象,则调用pretty::defaulted以获取给定类型的默认装饰。类obj将专门用于自定义默认装饰。

2. Target objects / containers

目标对象std::begin用于此代码的“漂亮装饰”是具有其中任何一个的对象

  • 重载std::endbegin(obj)定义(包括C风格数组),
  • 通过ADL获得end(obj)std::tuple
  • std::pair类型
  • 或类型begin

该代码包括用于识别具有范围特征的类的特征(end / begin(obj) == end(obj))。 (但是没有检查,operator<<是否是一个有效的表达。)

该代码在全局命名空间中提供operator<<s,该命名空间仅适用于没有更专业版std::string的类。因此,例如begin不使用此代码中的运算符打印,尽管具有有效的end / tuple对。

3. Utilization and customization

可以为每种类型(除了不同的std::vector<int>s)和流(不是流类型!)单独强加装饰。 (即,""可以为不同的流对象提供不同的装饰。)

A)默认装饰

默认前缀是", "(无),默认后缀是默认前缀,而默认前缀是pretty::defaulted(逗号+空格)。

B)通过专门化struct defaulted类模板来定制类型的默认装饰

decoration()有一个静态成员函数decor返回一个namespace pretty { template<class T, std::size_t N> struct defaulted<T[N]> { static decor<T[N]> decoration() { return{ { "(" }, { ":" }, { ")" } }; } }; } 对象,其中包含给定类型的默认值。

Example using an array:

自定义默认阵列打印:

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)

打印arry数组:

PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)

Using the char macro for namespace pretty { template< __VA_ARGS__ > struct defaulted< TYPE > { static decor< TYPE > decoration() { return { PREFIX, DELIM, POSTFIX }; } }; } streams

宏扩展到

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

使上述部分专业化能够被重写

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

或插入完整的专业化

wchar_t

PRETTY_DEFAULT_WDECORATION流的另一个宏包括:pretty::decoration

C)在溪流上施加装饰

函数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 用于在某个流上强加装饰。有一些重载 - 一个字符串参数是分隔符(从默认类中采用前缀和后缀) - 或三个字符串参数组合完整的装饰

Complete decoration for given type and stream

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

Customization of delimiter for given stream

std::tuple

4. Special handling of std::tuple<void*>

此代码不是允许对每个可能的元组类型进行专门化,而是将std::tuple<...>可用的任何装饰应用于所有类型的pretty::clears。

5. Remove custom decoration from the stream

要返回给定类型的默认装饰,请在流s上使用s << pretty::clear<std::vector<int>>(); 函数模板。

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;

5. Further examples

使用换行符分隔符打印“矩阵状”

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

打印

ideone/KKUebZ

#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_ 上看到它

6. Code

here

4
投票

我将在这里添加另一个答案,因为我已经提出了与前一个方法不同的方法,那就是使用区域设置方面。

基础知识是std::locale::facet

基本上你做的是:

  1. 创建一个派生自std::has_facet< MyPrettyVectorPrinter >的类。稍微缺点是你需要一个编译单元来保存它的id。我们称之为MyPrettyVectorPrinter。你可能会给它一个更好的名字,并为对和地图创建一个。
  2. 在你的流函数中,你检查std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  3. 如果返回true,则使用operator<<提取它
  4. 您的facet对象将具有分隔符的值,您可以阅读它们。如果未找到构面,则打印功能(the {fmt} library)提供默认构面。请注意,您可以为读取矢量执行相同的操作。

我喜欢这种方法,因为您可以使用默认打印,同时仍然可以使用自定义覆盖。

如果在多个项目中使用(不仅仅是标题),还需要注意创建新语言环境对象的费用这一事实的缺点是需要一个库。

我把它写成一个新的解决方案,而不是修改我的另一个,因为我相信这两种方法都是正确的,你可以选择。


4
投票

您可以使用#include <vector> #include <fmt/ranges.h> int main() { auto v = std::vector<int>{1, 2, 3}; fmt::print("{}", v); } 格式化容器以及范围和元组。例如:

{1, 2, 3}

版画

stdout

simple.h

免责声明:我是{fmt}的作者。


1
投票

我的解决方案是scc,它是{(a->b),(c->d)}包的一部分。所有标准容器,地图,集合,c阵列都是可打印的。


1
投票

这里的目标是使用ADL来定制我们如何打印。

传入格式化程序标记,并覆盖标记命名空间中的4个函数(之前,之后,之间和之后)。这会改变格式化程序在迭代容器时打印“装饰”的方式。

一个默认的格式化程序,它为地图执行(a,b,c),为tupleoids执行"hello",为字符串执行[x,y,z],为其他所有内容执行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>{}> {}; )。也许有一种更清洁的方法来做到这一点,不确定。

一个用于检测迭代性和元组的小库:

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

测试代码:

live example

_t

这确实使用了C ++ 14的特性(一些auto&&别名和http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html lambdas),但没有一个是必不可少的。


1
投票

来自第一个BoostCon(现在称为CppCon)之一,我和另外两个人在一个库上工作就是这样做的。主要的关键点是需要扩展名称空间std。结果证明这对于一个提升库来说是不可取的。

不幸的是,代码的链接不再有效,但你可能会在讨论中找到一些有趣的花絮(至少那些没有谈论命名的东西!)

qazxswpoi

© www.soinside.com 2019 - 2024. All rights reserved.