【问题标题】:Can range-based for loops be aware of the end?基于范围的 for 循环可以知道结尾吗?
【发布时间】:2012-08-25 09:13:24
【问题描述】:

给出最小的 C++11 STL 示例:

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

有没有办法检查 x 是否是结束前的那个?此示例中的目标是输出1,2,3,4,而不是最后的逗号。目前我使用带有两个迭代器的标准 for 循环,

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);

这很有效,但非常麻烦。有没有办法在基于范围的 for 循环中进行检查?

编辑:问题的重点是不是打印出逗号分隔的列表。我想知道基于范围的 for 循环是否对列表中的倒数第二个元素有任何了解(即它是否在结束前一个)。给出了最小的例子,所以我们都有一个共同的代码块可以讨论。

【问题讨论】:

  • 没有优雅的方法可以解决这个问题。将丑陋隐藏在函数中并继续前进。
  • @Shmiddty 不完全是,我使用示例作为输入,但我的问题是关于访问基于范围的 for 循环,以及它们对容器末尾的感知。
  • 这个特定问题已经用infix_ostream_iterator解决得更干净了。 copy(begin(S), end(S), infix_ostream_iterator&lt;int&gt;(cout, ","));。然而,就核心问题而言,我相信正确的答案很简短:“不”。
  • 你不能在右值上便携地调用--。使用std::prev。

标签: c++ stl c++11


【解决方案1】:

基于范围的 for 循环的真正目的是忘记迭代器。因此,它们只允许您访问当前值而不是迭代器。下面的代码会为你做吗?

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

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

cout << output;

编辑

另一种解决方案:您可以比较值的地址,而不是比较迭代器(就像对“正常” for 循环所做的那样):

set<int> S = {1,2,3,4};
auto &last = *(--S.end());
for (auto &x : S)
{
    cout << x;
    if (&x != &last)
        cout << ",";
}

【讨论】:

  • @ViralShah 请停止尝试在您的编辑中引入随机粗体字。这对任何人都没有帮助,只会让帖子变得丑陋。
  • 请注意,str() 返回存储字符串的一个副本,这使得上面的代码效率极低(最坏情况是二次的)。最好使用 bool 标志来指示相同的条件。
  • @Hooked 如何使用基于范围的 for 循环给出的值的地址,而不是比较迭代器?查看我的编辑。
  • 请注意,您可以使用&amp;(*S.rbegin()) 作为获取最后一个元素的更简单方法。
  • if (output.size()): 真是好技巧!但是我会用if(!output.empty()) 替换它,以避免每次迭代的大小计算。
【解决方案2】:

Boost.Range 可以在这里帮忙:

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

更灵活的方法是使用boost::adaptors::indexed(自 Boost 1.56 起)对范围进行索引:

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

在 1.56 之前的 Boost 版本中 boost::adaptors::indexed 将无法工作,但您可以轻松编写类似的工作:

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>();
}

这是使用来自Sequence-zip function for c++11? 的zip 函数

【讨论】:

  • 感谢提升方法,我总是很高兴看到这些类型的(尽管很复杂)解决方案 - 我学到了很多东西。无法与我之前的一个问题打成平手!
  • 可以说不需要手动解决方案; Boost.Range 为 OP 的确切目的提供了 indexed 范围适配器。
  • @LucDanton 不幸的是,indexed 不适用于循环范围;索引是迭代器上的一个方法,而不是通过间接获得。
  • @ecatmur 和来自 Google 的任何其他人,虽然这种情况不再适用,但您可以在 1.56 文档中看到一个 for-range-indexed 示例:boost.org/doc/libs/1_56_0/libs/range/doc/html/range/reference/…
  • @SamKellett 哇,这是一个非常大胆(破坏 API)的更改!我将为 Boost.Range 1.56 添加一个示例。
猜你喜欢
  • 1970-01-01
  • 2016-10-31
  • 1970-01-01
  • 1970-01-01
  • 2014-12-06
  • 2014-01-12
  • 2013-01-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多