Pretty-print C++ STL containers

前端 未结 10 1767
谎友^
谎友^ 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 08:07

    The goal here is to use ADL to do customization of how we pretty print.

    You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag's namespace. This changes how the formatter prints 'adornments' when iterating over containers.

    A default formatter that does {(a->b),(c->d)} for maps, (a,b,c) for tupleoids, "hello" for strings, [x,y,z] for everything else included.

    It should "just work" with 3rd party iterable types (and treat them like "everything else").

    If you want custom adornments for your 3rd party iterables, simply create your own tag. It will take a bit of work to handle map descent (you need to overload pretty_print_descend( your_tag to return pretty_print::decorator::map_magic_tag). Maybe there is a cleaner way to do this, not sure.

    A little library to detect iterability, and tuple-ness:

    namespace details {
      using std::begin; using std::end;
      template
      struct is_iterable_test:std::false_type{};
      template
      struct is_iterable_test())==end(std::declval()))
          , ((void)(std::next(begin(std::declval()))))
          , ((void)(*begin(std::declval())))
          , 1
        ))
      >:std::true_type{};
      templatestruct is_tupleoid:std::false_type{};
      templatestruct is_tupleoid>:std::true_type{};
      templatestruct is_tupleoid>:std::true_type{};
      // templatestruct is_tupleoid>:std::true_type{}; // complete, but problematic
    }
    templatestruct is_iterable:details::is_iterable_test>{};
    templatestruct is_iterable:std::true_type{}; // bypass decay
    templatestruct is_tupleoid:details::is_tupleoid>{};
    
    templatestruct is_visitable:std::integral_constant{}||is_tupleoid{}> {};
    

    A library that lets us visit the contents of an iterable or tuple type object:

    template
    std::enable_if_t{}> 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)(*b);
    }
    template
    std::enable_if_t{}> 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
      void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
      template
      void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
        std::forward(f)( std::get<0>( std::forward(tup) ) );
      }
      template
      void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
      template
      void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
        int unused[] = {0,((void)(
          f( std::get(std::forward(tup)) )
        ),0)...};
        (void)(unused);
      }
    }
    template
    std::enable_if_t{}> visit_first(Tup&& tup, F&& f) {
      details::visit_first( std::make_index_sequence< std::tuple_size>{} >{}, std::forward(tup), std::forward(f) );
    }
    template
    std::enable_if_t{}> visit_all_but_first(Tup&& tup, F&& f) {
      details::visit_all_but_first( std::make_index_sequence< std::tuple_size>{} >{}, std::forward(tup), std::forward(f) );
    }
    

    A pretty printing library:

    namespace pretty_print {
      namespace decorator {
        struct default_tag {};
        template
        struct map_magic_tag:Old {}; // magic for maps
    
        // Maps get {}s. Write trait `is_associative` to generalize:
        template
        void pretty_print_before( default_tag, std::basic_ostream& s, std::map const& ) {
          s << CharT('{');
        }
    
        template
        void pretty_print_after( default_tag, std::basic_ostream& s, std::map const& ) {
          s << CharT('}');
        }
    
        // tuples and pairs get ():
        template
        std::enable_if_t{}> pretty_print_before( default_tag, std::basic_ostream& s, Tup const& ) {
          s << CharT('(');
        }
    
        template
        std::enable_if_t{}> pretty_print_after( default_tag, std::basic_ostream& s, Tup const& ) {
          s << CharT(')');
        }
    
        // strings with the same character type get ""s:
        template
        void pretty_print_before( default_tag, std::basic_ostream& s, std::basic_string const& ) {
          s << CharT('"');
        }
        template
        void pretty_print_after( default_tag, std::basic_ostream& s, std::basic_string const& ) {
          s << CharT('"');
        }
        // and pack the characters together:
        template
        void pretty_print_between( default_tag, std::basic_ostream&, std::basic_string const& ) {}
    
        // map magic. When iterating over the contents of a map, use the map_magic_tag:
        template
        map_magic_tag pretty_print_descend( default_tag, std::map const& ) {
          return {};
        }
        template
        old_tag pretty_print_descend( map_magic_tag, C const& ) {
          return {};
        }
    
        // When printing a pair immediately within a map, use -> as a separator:
        template
        void pretty_print_between( map_magic_tag, std::basic_ostream& s, std::pair const& ) {
          s << CharT('-') << CharT('>');
        }
      }
    
      // default behavior:
      template
      void pretty_print_before( Tag const&, std::basic_ostream& s, Container const& ) {
        s << CharT('[');
      }
      template
      void pretty_print_after( Tag const&, std::basic_ostream& s, Container const& ) {
        s << CharT(']');
      }
      template
      void pretty_print_between( Tag const&, std::basic_ostream& s, Container const& ) {
        s << CharT(',');
      }
      template
      Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
        return std::forward(tag);
      }
    
      // print things by default by using <<:
      template
      std::enable_if_t{}> print( std::basic_ostream& os, Scalar&& scalar, Tag&&=Tag{} ) {
        os << std::forward(scalar);
      }
      // for anything visitable (see above), use the pretty print algorithm:
      template
      std::enable_if_t{}> print( std::basic_ostream& os, C&& c, Tag&& tag=Tag{} ) {
        pretty_print_before( std::forward(tag), os, std::forward(c) );
        visit_first( c, [&](auto&& elem) {
          print( os, std::forward(elem), pretty_print_descend( std::forward(tag), std::forward(c) ) );
        });
        visit_all_but_first( c, [&](auto&& elem) {
          pretty_print_between( std::forward(tag), os, std::forward(c) );
          print( os, std::forward(elem), pretty_print_descend( std::forward(tag), std::forward(c) ) );
        });
        pretty_print_after( std::forward(tag), os, std::forward(c) );
      }
    }
    

    Test code:

    int main() {
      std::vector 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

    This does use C++14 features (some _t aliases, and auto&& lambdas), but none are essential.

提交回复
热议问题