Pretty-print C++ STL containers

前端 未结 10 1775
谎友^
谎友^ 2020-11-21 07:39

Please take note of the updates at the end of this post.

Update: I have created a public project on GitHub for this library!


10条回答
  •  臣服心动
    2020-11-21 07:52

    The code proved to be handy on several occasions now and I feel the expense to get into customization as usage is quite low. Thus, I decided to release it under MIT license and provide a GitHub repository where the header and a small example file can be downloaded.

    http://djmuw.github.io/prettycc

    0. Preface and wording

    A 'decoration' in terms of this answer is a set of prefix-string, delimiter-string, and a postfix-string. Where the prefix string is inserted into a stream before and the postfix string after the values of a container (see 2. Target containers). The delimiter string is inserted between the values of the respective container.

    Note: Actually, this answer does not address the question to 100% since the decoration is not strictly compiled time constant because runtime checks are required to check whether a custom decoration has been applied to the current stream. Nevertheless, I think it has some decent features.

    Note2: May have minor bugs since it is not yet well tested.

    1. General idea/usage

    Zero additional code required for usage

    It is to be kept as easy as

    #include 
    #include "pretty.h"
    
    int main()
    {
      std::cout << std::vector{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
      return 0;
    }
    

    Easy customization ...

    ... with respect to a specific stream object

    #include 
    #include "pretty.h"
    
    int main()
    {
      // set decoration for std::vector for cout object
      std::cout << pretty::decoration>("(", ",", ")");
      std::cout << std::vector{1,2,3,4,5}; // prints (1,2,3,4,5)
      return 0;
    }
    

    or with respect to all streams:

    #include 
    #include "pretty.h"
    
    // set decoration for std::vector for all ostream objects
    PRETTY_DEFAULT_DECORATION(std::vector, "{", ", ", "}")
    
    int main()
    {
      std::cout << std::vector{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
      std::cout << pretty::decoration>("(", ",", ")");
      std::cout << std::vector{1,2,3,4,5}; // prints (1,2,3,4,5)
      return 0;
    }
    

    Rough description

    • The code includes a class template providing a default decoration for any type
    • which can be specialized to change the default decoration for (a) certain type(s) and it is
    • using the private storage provided by ios_base using xalloc/pword in order to save a pointer to a pretty::decor object specifically decorating a certain type on a certain stream.

    If no pretty::decor object for this stream has been set up explicitly pretty::defaulted::decoration() is called to obtain the default decoration for the given type. The class pretty::defaulted is to be specialized to customize default decorations.

    2. Target objects / containers

    Target objects obj for the 'pretty decoration' of this code are objects having either

    • overloads std::begin and std::end defined (includes C-Style arrays),
    • having begin(obj) and end(obj) available via ADL,
    • are of type std::tuple
    • or of type std::pair.

    The code includes a trait for identification of classes with range features (begin/end). (There's no check included, whether begin(obj) == end(obj) is a valid expression, though.)

    The code provides operator<<s in the global namespace that only apply to classes not having a more specialized version of operator<< available. Therefore, for example std::string is not printed using the operator in this code although having a valid begin/end pair.

    3. Utilization and customization

    Decorations can be imposed separately for every type (except different tuples) and stream (not stream type!). (I.e. a std::vector can have different decorations for different stream objects.)

    A) Default decoration

    The default prefix is "" (nothing) as is the default postfix, while the default delimiter is ", " (comma+space).

    B) Customized default decoration of a type by specializing the pretty::defaulted class template

    The struct defaulted has a static member function decoration() returning a decor object which includes the default values for the given type.

    Example using an array:

    Customize default array printing:

    namespace pretty
    {
      template
      struct defaulted
      {
        static decor decoration()
        {
          return{ { "(" }, { ":" }, { ")" } };
        }
      };
    }
    

    Print an arry 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)
    

    Using the PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) macro for char streams

    The macro expands to

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

    enabling the above partial specialization to be rewritten to

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

    or inserting a full specialization like

    PRETTY_DEFAULT_DECORATION(std::vector, "(", ", ", ")")
    

    Another macro for wchar_t streams is included: PRETTY_DEFAULT_WDECORATION.

    C) Impose decoration on streams

    The function pretty::decoration is used to impose a decoration on a certain stream. There are overloads taking either - one string argument being the delimiter (adopting prefix and postfix from the defaulted class) - or three string arguments assembling the complete decoration

    Complete decoration for given type and stream

    float e[3] = { 3.4f, 4.3f, 5.2f };
    std::stringstream u;
    // add { ; } decoration to u
    u << pretty::decoration("{", "; ", "}");
    
    // use { ; } decoration
    u << e << '\n'; // prints {3.4; 4.3; 5.2}
    
    // uses decoration returned by defaulted::decoration()
    std::cout << e; // prints 3.4, 4.3, 5.2
    

    Customization of delimiter for given stream

    PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")
    
    std::stringstream v;
    v << e; // prints {{{3.4,4.3,5.2}}}
    
    v << pretty::decoration(":");
    v << e; // prints {{{3.4:4.3:5.2}}}
    
    v << pretty::decoration("((", "=", "))");
    v << e; // prints ((3.4=4.3=5.2))
    

    4. Special handling of std::tuple

    Instead of allowing a specialization for every possible tuple type, this code applies any decoration available for std::tuple to all kind of std::tuple<...>s.

    5. Remove custom decoration from the stream

    To go back to the defaulted decoration for a given type use pretty::clear function template on the stream s.

    s << pretty::clear>();
    

    5. Further examples

    Printing "matrix-like" with newline delimiter

    std::vector> m{ {1,2,3}, {4,5,6}, {7,8,9} };
    std::cout << pretty::decoration>>("\n");
    std::cout << m;
    

    Prints

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

    See it on ideone/KKUebZ

    6. Code

    #ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
    #define pretty_print_0x57547_sa4884X_0_1_h_guard_
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #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 > {\
        static decor< TYPE, wchar_t, std::char_traits > 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  using _ol = std::integral_constant*;
        // SFINAE check whether T is a range with begin/end
        template
        class is_range
        {
          // helper function declarations using expression sfinae
          template  = nullptr>
          static std::false_type b(...);
          template  = nullptr>
          static auto b(U &v) -> decltype(begin(v), std::true_type());
          template  = nullptr>
          static std::false_type e(...);
          template  = nullptr>
          static auto e(U &v) -> decltype(end(v), std::true_type());
          // return types
          using b_return = decltype(b(std::declval()));
          using e_return = decltype(e(std::declval()));
        public:
          static const bool value = b_return::value && e_return::value;
        };
      }
    
      // holder class for data
      template>
      struct decor
      {
        static const int xindex;
        std::basic_string prefix, delimiter, postfix;
        decor(std::basic_string const & pre = "",
          std::basic_string const & delim = "",
          std::basic_string const & post = "")
          : prefix(pre), delimiter(delim), postfix(post) {}
      };
    
      template
      int const decor::xindex = std::ios_base::xalloc();
    
      namespace detail
      {
    
        template
        void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx)
        {
          using deco_type = decor;
          if (evt == std::ios_base::erase_event)
          { // erase deco
            void const * const p = s.pword(idx);
            if (p)
            {
              delete static_cast(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(p) };
              s.pword(idx) = static_cast(np);
            }
          }
        }
    
        template struct clearer {};
    
        template
        std::basic_ostream& operator<< (
          std::basic_ostream &s, clearer const &)
        {
          using deco_type = decor;
          void const * const p = s.pword(deco_type::xindex);
          if (p)
          { // delete if set
            delete static_cast(p);
            s.pword(deco_type::xindex) = nullptr;
          }
          return s;
        }
    
        template  
        struct default_data { static const CharT * decor[3]; };
        template <> 
        const char * default_data::decor[3] = { "", ", ", "" };
        template <> 
        const wchar_t * default_data::decor[3] = { L"", L", ", L"" };
    
      }
    
      // Clear decoration for T
      template
      detail::clearer clear() { return{}; }
      template
      void clear(std::basic_ostream &s) { s << detail::clearer{}; }
    
      // impose decoration on ostream
      template
      std::basic_ostream& operator<<(
        std::basic_ostream &s, decor && h)
      {
        using deco_type = decor;
        void const * const p = s.pword(deco_type::xindex);
        // delete if already set
        if (p) delete static_cast(p);
        s.pword(deco_type::xindex) = static_cast(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, deco_type::xindex);
          s.iword(deco_type::xindex) = 1;
        }
        return s;
      }
    
      template>
      struct defaulted
      {
        static inline decor decoration()
        {
          return{ detail::default_data::decor[0],
            detail::default_data::decor[1],
            detail::default_data::decor[2] };
        }
      };
    
      template>
      decor decoration(
        std::basic_string const & prefix,
        std::basic_string const & delimiter,
        std::basic_string const & postfix)
      {
        return{ prefix, delimiter, postfix };
      }
    
      template>
        decor decoration(
          std::basic_string const & delimiter)
      {
        using str_type = std::basic_string;
        return{ defaulted::decoration().prefix,
          delimiter, defaulted::decoration().postfix };
      }
    
      template>
        decor decoration(CharT const * const prefix,
          CharT const * const delimiter, CharT const * const postfix)
      {
        using str_type = std::basic_string;
        return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } };
      }
    
      template>
        decor decoration(CharT const * const delimiter)
      {
        using str_type = std::basic_string;
        return{ defaulted::decoration().prefix,
          str_type{ delimiter }, defaulted::decoration().postfix };
      }
    
      template
      struct tuple
      {
        template
        static void print(std::basic_ostream& s, T const & value,
          std::basic_string const &delimiter)
        {
          s << std::get(value) << delimiter;
          tuple::print(s, value, delimiter);
        }
      };
    
      template
      struct tuple
      {
        template
        static void print(std::basic_ostream& s, T const & value,
          std::basic_string const &) {
          s << std::get(value);
        }
      };
    
    }
    
    template
    std::basic_ostream & operator<< (
      std::basic_ostream &s, std::tuple<> const & v)
    {
      using deco_type = pretty::decor, CharT, TraitT>;
      using defaulted_type = pretty::defaulted, CharT, TraitT>;
      void const * const p = s.pword(deco_type::xindex);
      auto const d = static_cast(p);
      s << (d ? d->prefix : defaulted_type::decoration().prefix);
      s << (d ? d->postfix : defaulted_type::decoration().postfix);
      return s;
    }
    
    template
    std::basic_ostream & operator<< (
      std::basic_ostream &s, std::tuple const & v)
    {
      using deco_type = pretty::decor, CharT, TraitT>;
      using defaulted_type = pretty::defaulted, CharT, TraitT>;
      using pretty_tuple = pretty::tuple, 0U, sizeof...(T)-1U>;
      void const * const p = s.pword(deco_type::xindex);
      auto const d = static_cast(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
    std::basic_ostream & operator<< (
      std::basic_ostream &s, std::pair const & v)
    {
      using deco_type = pretty::decor, CharT, TraitT>;
      using defaulted_type = pretty::defaulted, CharT, TraitT>;
      void const * const p = s.pword(deco_type::xindex);
      auto const d = static_cast(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>
      typename std::enable_if < pretty::detail::is_range::value,
      std::basic_ostream < CharT, TraitT >> ::type & operator<< (
        std::basic_ostream &s, T const & v)
    {
      bool first(true);
      using deco_type = pretty::decor;
      using default_type = pretty::defaulted;
      void const * const p = s.pword(deco_type::xindex);
      auto d = static_cast 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_
    

提交回复
热议问题