cout a boost::range of elements

Go To StackoverFlow.com

1

Does Boost Ranges have a built-in way to cout the elements easily, for example separated by comma or space?

I am of course aware that I could loop over them and print them separately, but it seems to me that this should be built in somehow (like printing a vector in scripting languages).

In an example program I saw, the author copied the range to cout:

boost::copy(range, ostream_iterator(cout, " "))

Looks ugly to me. Is this idiomatic?

EDIT: A solution for std iterators would also be acceptable.

EDIT2: What I want to do is this:

cout << range;

But I don't think that works. So what I am hoping for is something like this (Python inspired):

cout << range.join(", ");

or perhaps with a range adaptor.

cout << (range | stringify(", "));
2012-04-04 16:53
by Gurgeh


4

I don't believe it's ever been really finished/polished, but that's the intent of Boost.Explore.

Personally, I've just gotten used to using std::copy. For this sort of thing, an infix_ostream_iterator can be quite helpful at times. For example, something like this:

#include <vector>
#include <iostream>
#include <algorithm>
#include <iterator>
#include "infix_iterator.h"

template <typename T>
std::ostream& operator<<(std::ostream &o, const std::vector<T>& v) { 
    o << "[";
    std::copy(v.begin(), v.end(), infix_ostream_iterator<T>(o, ", ")); 
    o << "]";
    return o; 
}

int main() { 

    std::vector<int> x;

    for (int i=0; i<20; i+=2)
        x.push_back(i);

    std::cout << x << "\n";
    return 0;
}

As it stands, this has operator<< taking a vector, but it would be relatively trivial to have it take a range instead.

2012-04-04 16:57
by Jerry Coffin
Thanks. Any particular reason why the STL and Boost do not define this for common containers? I knew about this method (never heard of boost explore, though), but always feel a little dirty defining helper functions when there might be more idiomatic ways :) - Gurgeh 2012-04-04 19:40
@Gurgeh: I'm not entirely sure. If it was made entirely generic, it might attempt to "take over" for things like input or output of a string, which could be a problem - Jerry Coffin 2012-04-04 19:46
That is not much of a reason (not blaming you for that, of course). Under normal circumstances it should not be hard to make the overloaded operators resolve in the correct order. And if there neverthless are obscure cases where something can go wrong, they could always define it manually for each of the standard containers. Anyway, I will choose your answer if something better does not turn up in a few hours. Thanks for the discussion - Gurgeh 2012-04-04 20:01


1

You might want to define a general function to output a forward iterable range. Here is join() function I used:

template<typename ForwardIter>
std::string join(const ForwardIter& it1, const ForwardIter& it2, const std::string& separator=" ") {
    if(it1 != it2) {
        std::ostringstream s;
        s << *it1;
        for(ForwardIter i=it1+1; i!=it2; i++)  s << separator << *i;
        return s.str();
    }
    else return "";
}

template<typename Range>
std::string join(const Range& r, const std::string& separator=" ") { return join(r.begin(), r.end(), separator); }

This function return a string and it should take any forward iterable range, such as list, vector, set, etc, in which the dereference of each element can be output individually by an ostream. With this function, you can use it like:

list<int> a = {1,2,3,4};
set<int> b = {4,3,2,1};
vector<int> c = {1,2,3,4};


cout << join(a) << endl;
cout << join(b, ",") << endl;
cout << join(c.begin(), c.begin()+2, "~") << endl;

I remember that the join() name might collide with some namespace, so you might want to change the name to similar name such as joining or joint. Also, you might want to use boost::begin(r) instead of r.begin() for better compatibility, if you ever use boost.

2012-04-04 19:41
by unsym
Ads