11

What is the most elegant way, in your opinion, to print to std::cout using std::ostream_iterator in C++11 and avoid printing a trailing delimeter?

The object I'm printing has bidirectional iterators, but not random access iterators.

std::list<double> x{1,2,3,4,5,6};
std::copy(x.begin(), std::prev(x.end()),
                std::ostream_iterator<int>(std::cout, ",") );
if ( x.size() != 0 )
  std::cout << *(--x.end()) << std::endl;
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
user
  • 7,123
  • 7
  • 48
  • 90
  • "*Most elegant*" is subjective by definition (read: not constructive). – ildjarn Jun 17 '12 at 18:55
  • @ildjarn I've edited to read "in your opinion", but I feel you're comment is a little pedantic: Do you really not think this is a useful question? I would have written "most concise" instead of "most elegant", but http://en.wikipedia.org/wiki/Kolmogorov_complexity is not computable. ; ) – user Jun 17 '12 at 19:00
  • 1
    There is no elegant way to do this. You have to make a check for some special case. Hide it in a function and move on. – R. Martinho Fernandes Jun 17 '12 at 19:00
  • @R.MartinhoFernandes Even if there is no one-liner, is the way I've written really what you would do? If so, that's enough for me. – user Jun 17 '12 at 19:01
  • 3
    If you already have a container, you could try the [pretty printer](http://stackoverflow.com/questions/4850473/pretty-print-c-stl-containers). – Kerrek SB Jun 17 '12 at 19:08
  • 3
    You should look at [infix_iterator](http://stackoverflow.com/a/3497021/906773). – Jesse Good Jun 17 '12 at 20:52

3 Answers3

10

Here's one of my favorites, but it doesn't use std::ostream_iterator:

#include <iterator>
#include <string>
#include <iosfwd>

template <class C>
auto
print(std::ostream& os, const C& c,
      const std::string& delim = std::string(", "),
      const std::string& open_brace = std::string("{"),
      const std::string& close_brace = std::string("}")
     ) -> decltype(std::begin(c), std::end(c), os)
{
    os << open_brace;
    auto i = std::begin(c);
    auto e = std::end(c);
    if (i != e)
    {
        os << *i;
        for (++i; i != e; ++i)
            os << delim << *i;
    }
    os << close_brace;
    return os;
}

#include <list>
#include <iostream>

int main()
{
    std::list<double> x{1,2,3,4,5,6};
    print(std::cout, x) << '\n';
}

{1, 2, 3, 4, 5, 6}

Update

Oliver goaded me into a challenge I couldn't resist. :-)

#include <iterator>
#include <string>
#include <iosfwd>

namespace my {

template <class C>
auto
print(std::ostream& os, const C& c,
      const std::string& delim = std::string(", "),
      const std::string& open_brace = std::string("{"),
      const std::string& close_brace = std::string("}")
     ) -> decltype(std::begin(c), std::end(c), os);

template <class C,
           typename std::enable_if
                    <
                       !std::is_same<C, std::string>::value,
                    bool>::type = false
         >
inline
auto
operator<< (std::ostream& os, const C& c) -> decltype(print(os, c))
{
    return print(os, c);
}

template <class C>
auto
print(std::ostream& os, const C& c,
      const std::string& delim,
      const std::string& open_brace,
      const std::string& close_brace
     ) -> decltype(std::begin(c), std::end(c), os)
{
    os << open_brace;
    auto i = std::begin(c);
    auto e = std::end(c);
    if (i != e)
    {
        os << *i;
        for (++i; i != e; ++i)
            os << delim << *i;
    }
    os << close_brace;
    return os;
}

}

#include <list>
#include <forward_list>
#include <iostream>

int main()
{
    std::forward_list<std::list<double>> x{{}, {3, 2, 1}, {1,2,3,4,5,6}};
    my::print(std::cout, x) << '\n';
}

{{}, {3, 2, 1}, {1, 2, 3, 4, 5, 6}}

It isn't perfect, but it was fun. :-) There's probably a better way to do it that would propagate the custom delimiter and braces more faithfully.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • +1 Is there a tweak to this so that it can print `list >`? Maybe recursively calling print on the inner objects if they're containers? – user Jun 17 '12 at 19:23
  • May I ask why is that `decltype(std::begin(c), std::end(c), os)` for? Couldn't the return value just be `std::ostream&`? – mfontanini Jun 17 '12 at 19:28
  • @Oliver: Yes, recursively calling print on the inner objects is probably the way to go. I slapped together an alternative just for fun. – Howard Hinnant Jun 17 '12 at 19:51
  • @HowardHinnant Super cool! This is going to be great for debugging (I always talk myself into just typing out the nested for loops "just this once". : ) – user Jun 17 '12 at 19:54
  • 1
    @mfontanini: I wanted to restrict the `print` function to just those types where `std::begin(c)` and `std::end(c)` are legal. This would enable me to later (for example) create a `print(std::ostream&, double)` overload and not have to worry that calls to this new overload would get mistakenly routed to the container version of `print(std::ostream&, const C&)`. I.e. if the `decltype` in the trailing return isn't valid for a specific instantiation of `C`, then that instantiation is SFINAE'd out. – Howard Hinnant Jun 17 '12 at 19:58
  • Wow, didn't know about decltype's SFINAE. Thanks! – mfontanini Jun 17 '12 at 20:27
3

Simply move back your cursor by:

std::cout << "\b";

and then overwrite the delimiter.

2

C++14 - unfortunately requires in/out stream:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <list>

int main()
{
    std::list<double> x{1,2,3,4,5,6};
    std::stringstream sstr;
    std::transform(x.begin(), x.end(), std::ostream_iterator<std::string>(sstr), [&](auto c){
        return (sstr.rdbuf()->in_avail() ? "," : "") + std::to_string(c);
    });
    std::cout << sstr.str() << std::endl;
}

live

Since C++17 there will be ostream_joiner which will fix this problem:

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

live

marcinj
  • 48,511
  • 9
  • 79
  • 100