17

Given the minimal C++11 STL example:

set<int> S = {1,2,3,4};
for(auto &x: S) {    
   cout << x;
   cout << ",";
}

Is there a way to check if x is the one right before the end? The goal in this example is to output 1,2,3,4 and not the final comma at the end. Currently I use a standard for loop with two iterators,

set<int>::const_iterator itr;
set<int>::const_iterator penultimate_end_itr = --S.end();
for(itr=S.begin(); itr!=penultimate_end_itr;++itr) 
    cout << (*itr) << ',';
cout << (*penultimate_end_itr);

Which works, but is terribly cumbersome. Is there a way to do the check within the range-based for loop?

EDIT: The point of the question is not to print out a comma separated list. I want to know if a range-based for loop has any knowledge of the penultimate element in the list (i.e. is it one before the end). The minimal example was presented so we all have a common code block to talk about.

Hooked
  • 84,485
  • 43
  • 192
  • 261
  • 8
    There is no elegant way to solve this problem. Hide the ugly in a function and move on. – R. Martinho Fernandes Aug 30 '12 at 15:27
  • Possible duplicate of http://stackoverflow.com/questions/1430757/c-array-to-string – Shmiddty Aug 30 '12 at 15:28
  • 2
    @Shmiddty not quite, I'm using the sample example as input but my question is about accessing range-based for loops, and their awareness to the end of the container. – Hooked Aug 30 '12 at 15:29
  • 5
    This *specific* problem has been solved much more cleanly using [infix_ostream_iterator](http://codereview.stackexchange.com/questions/13176/infix-iterator-code). `copy(begin(S), end(S), infix_ostream_iterator(cout, ","));`. As far as the core question goes, however, I believe the correct answer is short and simple: "no." – Jerry Coffin Aug 30 '12 at 15:41
  • 1
    You can't portably call `--` on an rvalue. Use `std::prev`. – ecatmur Aug 30 '12 at 16:43

2 Answers2

19

The very purpose of range-based for loops is to forget the iterator. As such, they only allow you access to the current value and not the iterator. Would the following code do it for you?

set<int> S = {1,2,3,4};

std::string output;
for(auto &x: S) {    
   if (!output.empty())
       output += ",";
    output += to_string(x);
  }

cout << output;

EDIT

Another solution: Instead of comparing iterators (as one would do with "normal" for loops), you could compare the addresses of the values:

set<int> S = {1,2,3,4};
auto &last = *(--S.end());
for (auto &x : S)
{
    cout << x;
    if (&x != &last)
        cout << ",";
}
user1202136
  • 11,171
  • 4
  • 41
  • 62
  • 1
    @ViralShah Please stop attempting to introduce random bolding of words in your edits. That isn't helping anybody, it just makes the post ugly. – user229044 Aug 30 '12 at 15:35
  • 1
    Note that `str()` returns *a copy* of the stored string, which makes the above code extremely inefficient (worst case quadratic). Better to use a bool flag to indicate the same condition. – Yakov Galka Aug 30 '12 at 15:48
  • @Hooked How about using addresses of the values given by the range-based for loop, instead of comparing iterators? See my edit. – user1202136 Aug 30 '12 at 16:01
  • 3
    Note that you could do `&(*S.rbegin())` as a simpler way to get the last element. – GManNickG Aug 30 '12 at 17:27
  • 1
    `if (output.size())`: Really nice trick ! However I would replace it by `if(!output.empty())` to avoid size computation for each iteration. – Steven Aug 25 '16 at 16:30
7

Boost.Range can help out here:

if (std::begin(S) != std::end(S)) {
    std::cout << *std::begin(S);
    for (const auto &x: boost::make_iterator_range(std::next(std::begin(S)), std::end(S))) {
        std::cout << ", " << x;
    }
}

A much more flexible approach is to index the range, using boost::adaptors::indexed (since Boost 1.56):

for (const auto &element: boost::adaptors::index(S)) {
    std::cout << (element.index() ? ", " : "") << element.value();
}

In versions of Boost prior to 1.56 boost::adaptors::indexed won't work but you can easily write a work-alike:

template <typename... T>
auto zip(const T&... ranges) -> boost::iterator_range<boost::zip_iterator<decltype(boost::make_tuple(std::begin(ranges)...))>>
{
    auto zip_begin = boost::make_zip_iterator(boost::make_tuple(std::begin(ranges)...));
    auto zip_end = boost::make_zip_iterator(boost::make_tuple(std::end(ranges)...));
    return boost::make_iterator_range(zip_begin, zip_end);
}

template<typename T>
auto enumerate(const T &range) -> boost::iterator_range<boost::zip_iterator<boost::tuple<
    boost::counting_iterator<decltype(boost::distance(range))>, decltype(std::begin(range))>>>
{
    return zip(boost::make_iterator_range(boost::make_counting_iterator(0),
        boost::make_counting_iterator(boost::distance(range))), range);
}

for (const auto &tup: enumerate(S)) {
    std::cout << (tup.get<0>() ? ", " : "") << tup.get<1>();
}

This is using the zip function from Sequence-zip function for c++11?

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thanks for the boost approach, I always appreciate seeing these types of (albeit complicated) solutions - I learn so much. Can't beat the tie in with one of my previous questions! – Hooked Aug 30 '12 at 18:15
  • There is arguably no need for a hand-rolled solution; Boost.Range provides the [indexed](http://www.boost.org/doc/libs/1_51_0/libs/range/doc/html/range/reference/adaptors/reference/indexed.html) range adaptors for the exact purposes of the OP. – Luc Danton Aug 30 '12 at 20:14
  • 1
    @LucDanton unfortunately `indexed` doesn't work with range for loops; the index is a method on the iterator rather than being available through indirection. – ecatmur Aug 31 '12 at 10:21
  • 1
    @ecatmur and anybody else from Google, while this was the case it isn't true anymore, as you can see there is a for-range-indexed example in the 1.56 docs: http://www.boost.org/doc/libs/1_56_0/libs/range/doc/html/range/reference/adaptors/reference/indexed.html – Sam Kellett Apr 28 '15 at 10:12
  • 1
    @SamKellett wow, that's a pretty bold (breaking API) change! I'll add an example for Boost.Range 1.56. – ecatmur Apr 28 '15 at 10:21