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

限于喜欢 提交于 2021-02-18 09:51:30

问题


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.


回答1:


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 << ",";
}



回答2:


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?



来源:https://stackoverflow.com/questions/12200313/can-range-based-for-loops-be-aware-of-the-end

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!