web-dev-qa-db-ja.com

プリティプリントC ++ STLコンテナー

この投稿の最後にある更新に注意してください。

更新:このライブラリ用に 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から素晴らしい回答を得ました。SFVENE型の特性と組み合わせて、完全に一般的でネスト可能な方法で問題を解決するようです。区切り文字は個別に特殊化できます。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>の出力を実装します。 更新:これは SOに関する別の質問Upupdate:これは、Xeoのおかげで実装されました!
  • ヘルパークラスがグローバルネームスペースに流れ込まないように、ネームスペースを追加します。 完了
  • テンプレートエイリアス(または類似のもの)を追加して、カスタム区切りクラス、またはプリプロセッサマクロを作成しやすくしますか?

最近の更新:

  • Print関数の単純なforループを支持して、カスタム出力イテレーターを削除しました。
  • 実装の詳細はすべてpretty_print名前空間に含まれています。グローバルストリーム演算子とpretty_print_arrayラッパーのみがグローバル名前空間にあります。
  • operator<<stdに正しくなるように名前空間を修正しました。

注:

  • 出力イテレータを削除すると、std::copy()を使用してきれいに印刷する方法がなくなります。これが望ましい機能であれば、きれいなイテレータを復活させるかもしれませんが、以下のSvenのコードには実装があります。
  • オブジェクト定数ではなく、区切り文字をコンパイル時定数にすることは、設計上の意識的な決定でした。つまり、実行時に区切り文字を動的に指定することはできませんが、不要なオーバーヘッドがないことも意味します。オブジェクトベースのデリミタ設定は、以下のSvenのコードへのコメントでDennis Zickefooseによって提案されました。必要に応じて、これを代替機能として実装できます。
  • 現在、ネストされたコンテナの区切り文字をカスタマイズする方法は明らかではありません。
  • このライブラリの目的は、ゼロコーディングを必要とするquickコンテナ印刷機能を許可することです。これは、汎用のフォーマットライブラリではなく、コンテナ検査用の定型コードを記述する必要性を軽減するための開発ツールです。

貢献してくれた皆さん、ありがとう!


注:カスタム区切り文字を展開する簡単な方法を探している場合、ここでは型消去を使用する1つの方法を示します。次のように、MyDelなどの区切りクラスをすでに構築していると仮定します。

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

ここで、これらの区切り文字を使用して、いくつかのコンテナvstd::cout << MyPrinter(v) << std::endl;を記述できるようにします。 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); }
370
Kerrek SB

このソリューションは、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_iteratorと同様に機能しますが、最後の項目の後に区切り文字を出力しません。コンテナのフォーマットはprint_container_helperによって行われます。これは、is_containerトレイトなしでコンテナを印刷したり、異なる区切り文字タイプを指定したりするために直接使用できます。

また、is_containerとdelimitersを定義したので、非標準の述語またはアロケーターを持つコンテナー、およびcharとwchar_tの両方で機能します。 operator <<関数自体も、charストリームとwchar_tストリームの両方で機能するように定義されています。

最後に、std::enable_ifを使用しました。これは、C++ 0xの一部として利用可能で、Visual C++ 2010およびg ++ 4.3(-std = c ++ 0xフラグが必要)以降で動作します。この方法では、Boostに依存しません。

81
Sven

これは数回編集されており、コレクションRangePrinterをラップするメインクラスを呼び出すことにしました。

これは、1回限りのoperator <<オーバーロードを記述したら、コレクションで自動的に機能するはずです。ただし、マップをペアで印刷するために特別なものが必要で、区切り文字をカスタマイズする場合があります。

アイテムを直接出力する代わりに、アイテムで使用する特別な「印刷」機能を使用することもできます。 STLアルゴリズムに少し似ているので、カスタム述語を渡すことができます。マップでは、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に使用できます

 std::cout << outputFormatter( mySet );

また、カスタムプリンターと2つのイテレーターを使用するフリー機能バージョンを作成することもできます。いずれにせよ、テンプレートパラメータが解決され、一時パラメータとして渡すことができます。

22
CashCow

完全な作業プログラムとして提示された作業用ライブラリを次に示します。

#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[]で問題が発生する可能性があります。

15
Marcelo Cantos

このコードは、いくつかの場面で便利であることが判明しました。使用量が非常に少ないため、カスタマイズにかかる費用を感じています。したがって、MITライセンスでリリースし、ヘッダーと小さなサンプルファイルをダウンロードできるGitHubリポジトリを提供することにしました。

http://djmuw.github.io/prettycc

0.序文と文言

この回答に関して 'decoration'は、prefix-string、delimiter-string、およびpostfix-stringのセットです。コンテナの値の前にプレフィックス文字列がストリームに挿入され、コンテナの値の後に後置文字列が挿入される場所(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;
}

大まかな説明

  • コードには、あらゆるタイプのデフォルトの装飾を提供するクラステンプレートが含まれています
  • これは、特定のタイプのデフォルト装飾を変更するために特化することができます。
  • ios_baseが提供するプライベートストレージを使用するxalloc/pwordを使用して、特定のストリームの特定の型を具体的に修飾するpretty::decorオブジェクトへのポインターを保存します。

このストリームのpretty::decor<T>オブジェクトが明示的にセットアップされていない場合、pretty::defaulted<T, charT, chartraitT>::decoration()が呼び出されて、指定されたタイプのデフォルトの装飾が取得されます。クラスpretty::defaultedは、デフォルトの装飾をカスタマイズするために特化されます。

2.ターゲットオブジェクト/コンテナ

このコードの 'pretty decoration'のターゲットオブジェクトobjは、次のいずれかを持つオブジェクトです。

  • オーバーロードstd::beginおよびstd::endを定義(Cスタイル配列を含む)、
  • begin(obj)およびend(obj)がADL経由で利用可能
  • タイプはstd::Tupleです
  • またはstd::pairタイプ。

コードには、範囲機能(begin/end)を持つクラスを識別するための特性が含まれています。 (ただし、begin(obj) == end(obj)が有効な式であるかどうかのチェックは含まれていません。)

コードは、operator<<のより特化したバージョンが利用できないクラスにのみ適用されるoperator<<sをグローバル名前空間に提供します。したがって、たとえば、有効なbegin/endのペアがありますが、このコードの演算子を使用してstd::stringは出力されません。

3.利用とカスタマイズ

装飾は、すべてのタイプ(異なるTuplesを除く)およびストリーム(ストリームタイプではありません!)に個別に適用できます。 (つまり、std::vector<int>は、ストリームオブジェクトごとに異なる装飾を持つことができます。)

A)デフォルトの装飾

デフォルトのプレフィックスはデフォルトの接尾辞と同様に""(なし)で、デフォルトの区切り文字は", "(コンマ+スペース)です。

B)pretty::defaultedクラステンプレートを特化して、型のデフォルトの装飾をカスタマイズしました

struct defaultedには、特定のタイプのデフォルト値を含むdecorオブジェクトを返す静的メンバー関数decoration()があります。

配列を使用した例:

デフォルトの配列印刷をカスタマイズします。

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

Arry配列を出力します:

float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
std::cout << e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2)

charストリームにPRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)マクロを使用する

マクロは次のように展開されます

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は、特定のストリームに装飾を課すために使用されます。区切り文字である1つの文字列引数(既定のクラスからプレフィックスとポストフィックスを採用)-または完全な装飾を組み立てる3つの文字列引数のいずれかを取るオーバーロードがあります。

指定されたタイプとストリームの完全な装飾

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の特別な処理

このコードは、可能なすべてのTupleタイプに特化を許可する代わりに、std::Tuple<void*>で利用可能な装飾をすべての種類のstd::Tuple<...>sに適用します。

5.ストリームからカスタム装飾を削除する

特定のタイプのデフォルトの装飾に戻るには、ストリームspretty::clear関数テンプレートを使用します。

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_
6
Pixelchemist

{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}の著者です。

4
vitaut

以前のアプローチとは異なるアプローチを考え出したので、ここで別の答えを追加します。それはロケールファセットを使用することです。

基本は ここ です

基本的にあなたがすることは:

  1. std::locale::facetから派生するクラスを作成します。わずかな欠点は、そのidを保持するためにどこかにコンパイルユニットが必要になることです。 MyPrettyVectorPrinterと呼びましょう。おそらく、より良い名前を付けて、ペアとマップ用の名前を作成します。
  2. ストリーム関数で、std::has_facet< MyPrettyVectorPrinter >をチェックします
  3. それがtrueを返す場合、std::use_facet< MyPrettyVectorPrinter >( os.getloc() )でそれを抽出します
  4. ファセットオブジェクトには区切り文字の値があり、それらを読み取ることができます。ファセットが見つからない場合、印刷関数(operator<<)はデフォルトのものを提供します。ベクトルの読み取りでも同じことができることに注意してください。

カスタムオーバーライドを使用しながら、デフォルトの印刷を使用できるため、この方法が気に入っています。

欠点は、複数のプロジェクトで使用する場合、ファセット用のライブラリが必要なことです(したがって、ヘッダーのみにすることはできません)。また、新しいロケールオブジェクトを作成するための費用に注意する必要があります。

どちらのアプローチも正しいと思いますので、他のソリューションを修正するのではなく、これを新しいソリューションとして作成しました。

4
CashCow

ここでの目標は、ADLを使用して、きれいに印刷する方法をカスタマイズすることです。

フォーマッタタグを渡し、タグのネームスペースの4つの関数(前、後、間、下)をオーバーライドします。これにより、コンテナを反復処理する際にフォーマッタが「装飾」を印刷する方法が変わります。

マップ用の{(a->b),(c->d)}、タプルロイド用の(a,b,c)、文字列用の"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>{}> {};

反復可能または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&&ラムダ)を使用しますが、不可欠なものはありません。

私の解決策は simple.h です。これは scc パッケージの一部です。すべてのstdコンテナ、マップ、セット、C配列は印刷可能です。

1

最初のBoostCon(現在はCppConと呼ばれます)の1つから出てきて、私と他の2人はまさにこれを行うためにライブラリに取り組んでいました。主なこだわりは、名前空間stdを拡張する必要があったことです。これは、ブーストライブラリを使用することはできませんでした。

残念ながら、コードへのリンクはもう機能しませんが、議論の中でいくつかの興味深い情報を見つけるかもしれません(少なくとも、それを何と名付けるかについて話していません!)

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

1
Jeffrey Faust