Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can range-based for loops be aware of the end?

Tags:

c++

c++11

stl

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.

like image 734
Hooked Avatar asked Aug 30 '12 15:08

Hooked


2 Answers

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 << ",";
}
like image 118
user1202136 Avatar answered Oct 18 '22 23:10

user1202136


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?

like image 30
ecatmur Avatar answered Oct 18 '22 21:10

ecatmur