Query of interest for a container output library

Hello, lately I've been working on a library that can output different kinds of containers to standard output streams. The library is currently completely generic and can output any object that models the input range concept or Boost.Fusion's sequence concept. It doesn't restrict itself to std containers. If an object looks like a sequence, then it is a sequence in the eyes of the library. The library uses different open and close characters for different kinds of containers. For example the output of the following: std::cout << std::vector<int> {1,2,3} << '\n'; std::cout << std::set<int> {1,2,3} << '\n'; std::cout << std::map<int,int> { {1,2}, {2,3}, {3,1} } << '\n'; is: [1,2,3] {1,2,3} {(1,2), (2,3), (3,1)} In the example std::vector is interpreted as an ordered_dynamic container and std::set and std::map are interpreted as associative_dynamic containers by the library. The library recursively outputs the contents of each container, so because std::map::value_type is a std::pair (which models Boost.Fusion's sequence concept) it can also output that. std::pair is interpreted as an ordered_fixed container by the library. The library picks the appropriate category for each type being outputted by using Boost.MPL to introspect the type of the object being outputted. In the future I would like to extend the library to offer facilities to help users implement a operator<< (std::ostream&, T) function for arbitrary types but I haven't really given that a lot of thought yet. The library's name is currently Boost.Stringize but I don't really like that name and I'm really open for suggestions. Overall I think this library would make a good addition to Boost as it simplifies things for the end user. If a user wants to output a vector, he/she shouldn't have to iterate over it. It should be as simple as outputting an int. In Python a user can just "print [1,2,3]". Why can't we have something similar in C++? Is there any interest for this library in Boost? Thanks, Tal Zion -- View this message in context: http://boost.2283326.n4.nabble.com/Query-of-interest-for-a-container-output-... Sent from the Boost - Dev mailing list archive at Nabble.com.

On Wed, Jan 30, 2013 at 6:45 PM, meta221 <talzion12@gmail.com> wrote:
Hello, lately I've been working on a library that can output different kinds of containers to standard output streams. The library is currently completely generic and can output any object that models the input range concept or Boost.Fusion's sequence concept. It doesn't restrict itself to std containers. If an object looks like a sequence, then it is a sequence in the eyes of the library.
The library uses different open and close characters for different kinds of containers. For example the output of the following: std::cout << std::vector<int> {1,2,3} << '\n'; std::cout << std::set<int> {1,2,3} << '\n'; std::cout << std::map<int,int> { {1,2}, {2,3}, {3,1} } << '\n'; is: [1,2,3] {1,2,3} {(1,2), (2,3), (3,1)}
In the example std::vector is interpreted as an ordered_dynamic container and std::set and std::map are interpreted as associative_dynamic containers by the library. The library recursively outputs the contents of each container, so because std::map::value_type is a std::pair (which models Boost.Fusion's sequence concept) it can also output that. std::pair is interpreted as an ordered_fixed container by the library. The library picks the appropriate category for each type being outputted by using Boost.MPL to introspect the type of the object being outputted.
In the future I would like to extend the library to offer facilities to help users implement a operator<< (std::ostream&, T) function for arbitrary types but I haven't really given that a lot of thought yet.
The library's name is currently Boost.Stringize but I don't really like that name and I'm really open for suggestions.
Overall I think this library would make a good addition to Boost as it simplifies things for the end user. If a user wants to output a vector, he/she shouldn't have to iterate over it. It should be as simple as outputting an int. In Python a user can just "print [1,2,3]". Why can't we have something similar in C++?
Is there any interest for this library in Boost?
Does the library define operator<< for the containers themselves? Does it support arbitrary ranges (e.g. iterator_range<T>)? It really worries me that in the examples you presented you create containers rather than some manipulator that refers to the (external) container or range. This implies that you don't have to copy the container or elements in order to output them. I intend to provide different manipulators, including the one for logging ranges, in Boost.Log eventually but I haven't got to it yet.

The library defines a templated operator<< that is enabled (by Boost.EnableIf) for all types that have a const_iterator typedef, or evaluate to true in boost::fusion::is_sequence<T> and are not already ostreamable. boost::iterator_range already defines operator<< and so the library will "back down" and let iterator_range do its job. The library tries not to interfere with other objects. If another object says it can be outputted to an ostream, the library "believes" it. By using this philosophy, the library prevents any ambiguity in operator<<. But to answer your question, the library is completely generic and it does support arbitrary ranges. Here are some code snippets that show how the library captures a range: template <typename T> struct is_ostreamable : has_left_shift<std::ostream&, T const&> {}; struct sequence { BOOST_MPL_HAS_XXX_TRAIT_NAMED_DEF(has_const_iterator, const_iterator, false) /** * Determines if a type is a sequence. */ template <typename I, typename T=typename remove_reference<I>::type > struct is : mpl::or_< has_const_iterator<T>, fusion::traits::is_sequence<T> > {}; }; /**** IN GLOBAL NAMESPACE ****/ template <typename T> typename boost::enable_if< boost::mpl::and_< boost::stringize::detail::sequence::is<T>, boost::mpl::not_< // ignore everything that is ostreamable already boost::stringize::detail::is_ostreamable<T> > >, std::ostream&
::type operator<< (std::ostream& out, T const& t) { return boost::stringize::detail::print(out, t); }
This code may change in the future to check if T::value_type is also ostreamable. -- View this message in context: http://boost.2283326.n4.nabble.com/Query-of-interest-for-a-container-output-... Sent from the Boost - Dev mailing list archive at Nabble.com.

On Wed, Jan 30, 2013 at 7:27 PM, Tal Zion <talzion12@gmail.com> wrote:
The library defines a templated operator<< that is enabled (by Boost.EnableIf) for all types that have a const_iterator typedef, or evaluate to true in boost::fusion::is_sequence<T> and are not already ostreamable.
boost::iterator_range already defines operator<< and so the library will "back down" and let iterator_range do its job. The library tries not to interfere with other objects. If another object says it can be outputted to an ostream, the library "believes" it. By using this philosophy, the library prevents any ambiguity in operator<<.
But to answer your question, the library is completely generic and it does support arbitrary ranges.
Here are some code snippets that show how the library captures a range:
[snip] I'm not familiar with has_left_shift behavior but from the look of it it seems that it checks for availability of operator<< with a particular signature. If so, the is_ostreamable check is unreliable because there may be a suitable operator<< to be found by ADL or otherwise which does not match the signature. Anyway, I feel very uncomfortable about defining such blanket operators because it can interfere with user's code and intentions. I would prefer the manipulator-based approach.

I think a solution similar to to_string() overloads would be better and would let the user define it's << operator easily if he wants. For example std::cout << to_string(std::vector<int> {1,2,3}) << '\n'; std::cout << to_string(std::set<int> {1,2,3}) << '\n'; std::cout << to_string(std::map<int,int> { {1,2}, {2,3}, {3,1} }) << '\n'; is: [1,2,3] {1,2,3} {(1,2), (2,3), (3,1)} It could be another function name, but I agree that adding << overloads might not be the best way to do it. It makes things more explicit. Joel Lamotte

You might be right. I too thought overloading operator<< might be problematic but I haven't encountered any problems yet. I think that std::cout << std::vector<int> {1,2,3} << '\n'; is simpler than the alternative, but I see why people would be alarmed. I think simplicity is worth the extra work and I will look into the subject. For now I added a str method under boost::stringize and I put the global operator<< under #ifdef BOOST_STRINGIZE_GLOBAL_LEFT_SHIFT_OPERATOR. Ideas regarding how to define a safe global operator<< would be much appreciated. As has been said, currently I use Boost.EnableIf to enable/disable the overloads. I use Boost.TypeTraits and Boost.MPL to introspect the type of the object and disable if has_left_shift is already defined and enable only for types that have a const_iterator typedef or are fusion sequences. Currently the overloads are in the global namespace. P.S. Regarding the str method, for efficiency's sake it returns a detail::feeder object which simply holds a reference to the input and defines operator<< and operator std::string() as well as some other operators. Returning a string from boost::stringize::str would be inefficient if the user wants to just stream the object. Tal Zion -- View this message in context: http://boost.2283326.n4.nabble.com/Query-of-interest-for-a-container-output-... Sent from the Boost - Dev mailing list archive at Nabble.com.

-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Tal Zion Sent: Thursday, January 31, 2013 4:26 AM To: boost@lists.boost.org Subject: Re: [boost] Query of interest for a container output library -- View this message in context: http://boost.2283326.n4.nabble.com/Query-of-interest-for-a-container- output-library-tp4641997p4642051.html
I don't want to muddy this conversation, but I wonder if you are ignoring Steven Watanabe's type_erasure library (perhaps because it's highly un- or anti-descriptive name). http://www.boost.org/doc/libs/1_52_0/doc/html/container/containers_of_incomp... It provides what many users want to list containers without much tiresome typing or any troublesome 'typing'. But (at the price of some compile time) it *also provides very easy (and quite fancy) formatting*. Steven's simple example is: int test[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; separator_printer p1(", "); // Construct a sequence printer with comma separator. p1.print(std::cout, test); // Outputs: 1,2,3,4,5,6,7,8,9,10 where test could be any ostreamable container. but with a little more work one can produce other 'printers' which give a lot of layout control using parameters, for example: decor_printer p1decor("int[10] = {", ", ", "};\n"); // Construct a sequence printer with prefix {, comma separator and } newline terminator. A few typical (and more fancy) examples of output are: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 1, 2, 3, 4, 5, 6, 7, 8, 9, 10| int[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 1___2___3___4___5___6___7___8___9___10 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +1.0000, +2.0000, +3.0000, +4.0000, +5.0000, +6.0000, +7.0000, +8.0000, +9.0000, +10.000 2345.6 m, 123.40 m, 0.012300 m 45210.(+/-1234.0) m, 789.00(+/-2.5000) m, 0.00056700(+/-2.3400e-005) m one, two, three, four, five, six, seven, eight, nine, ten ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN 1,2,3,4, 5,6,7,8, 9,10 1 4 7 10 2 5 8 3 6 9 1.0000, 2.0000, 3.0000, 4.0000, 5.0000, 6.0000, 7.0000, 8.0000, 9.0000, 10.000 1.0000,2.0000,3.0000,4.0000, 5.0000,6.0000,7.0000,8.0000, 9.0000,10.000 3.0000,4.0000,5.0000,6.0000, 7.0000 0, -2.0000 1, 9.9000 1, 9.9000 4, 6.6000 1, 9.9000 2, 8.8000 3, 7.7000 4, 6.6000 1, 9.9000,2, 8.8000,3, 7.7000,4, 6.6000 1.23 +/-0.056 (42) 1.23 +/-0.056 (42),4.56 +/-0.021 (99) 1.23 +/-0.056 (42) First #1, 2012-Aug-10 10:03:35 4.56 +/-0.021 (99) Second #1, 2012-Aug-08 10:01:56 1.23 +/-0.056 (42) First #1, 2012-Aug-10 10:03:35,4.56 +/-0.021 (99) Second #1, 2012-Aug-08 10:01:56 Some are probably messed up by the browser but you will get the flavour. Paul

On 1/30/2013 9:45 AM, meta221 wrote:
Hello, lately I've been working on a library that can output different kinds of containers to standard output streams.
... Have you investigated the Container Printing Lib from boostcon 2007? IIRC, this was modified over a few years and was submitted for review, but was not accepted. Some comments being: If it can output what about input? and Isn't this best done with boost::spirit karma for printing and qi for input? Jeff

Jeff Flinn wrote:
On 1/30/2013 9:45 AM, meta221 wrote:
Hello, lately I've been working on a library that can output different kinds of containers to standard output streams.
...
Have you investigated the Container Printing Lib from boostcon 2007?
IIRC, this was modified over a few years and was submitted for review, but was not accepted. Some comments being: If it can output what about input? and Isn't this best done with boost::spirit karma for printing and qi for input?
Jeff
It's not clear to me how this differs from the log example in the serialization library Robert Ramey

Have you investigated the Container Printing Lib from boostcon 2007?
I looked into Boost.Explore after I started working on this project. My thoughts were that I could improve it by writing a more generic library which works with all types of containers. The fact that it worked with only specific containers was odd to me. It seemed to me that Boost.Explore was not under active development, so I wrote my own solution, mainly because I didn't know who to contact. I really liked the name Boost.Explore.
If it can output what about input? and Isn't this best done with boost::spirit karma for printing and qi for input?
This library so far has been about simplicity for end users. I think that in this case simplicity is a big factor because streaming containers should be easy. What makes a library great is the ability to be used simply in simple situations and still offer powerful tools for more elaborate problems. Boost.Spirit is a very powerful and complex tool which might not always fit the need for simple container output. When I first started learning Boost.Spirit it took me a few good days to understand what was going on. DSELs (domain specific embedded languages) were very new and confusing to me. Also Boost.Spirit's complexity imposes dramatically increased compilation times. In my opinion for simple problems there should be simple solutions. Boost.Spirit is a great tool, but seems to me like an overkill for simple output of containers. It might be a good idea to integrate Boost.Spirit.Karma generators to the library for more complex needs. Input doesn't seem to me in the scope of this library, but my mind isn't completely made up about the subject. Tal Zion -- View this message in context: http://boost.2283326.n4.nabble.com/Query-of-interest-for-a-container-output-... Sent from the Boost - Dev mailing list archive at Nabble.com.
participants (7)
-
Andrey Semashev
-
Jeff Flinn
-
Klaim - Joël Lamotte
-
meta221
-
Paul A. Bristow
-
Robert Ramey
-
Tal Zion