13

C++11 引入了基于范围的 for 循环,该循环在内部使用 (const) 迭代器实现,因此:

std::vector<std::string> vec;

for(std::string &str : vec)
{
//...
}

基本上相当于更详细(是的,可以使用 简化auto):

for(std::vector<std::string>::iterator it = vec.begin(); it != vec.end(); ++it)
{
//...
}

然而,通常人们也需要该项目的索引。第二种方法很简单:

auto index = it - vec.begin();

在基于范围的for情况下,它并不是那么简单。但是我想知道这是否可以完全避免迭代器的可移植解决方案:

for(auto &str : vec)
{
    auto index = &str - &vec[0];
}

const版本将相同,但需要注意不要将非const容器与 const 引用混合,这可能并不总是很明显。)

显然,这依赖于几个假设:

  • vector 的迭代器只是对项目的引用(可能在标准中?)

  • 容器保证连续(std::vector是...)

  • 基于范围的内部实现(也可能在标准中)

4

2 回答 2

18

是的,但我会vec.data()改用。使用的一个好处.data()是非连续std容器没有它,因此当被迭代的容器不能以这种方式工作时(如dequeor std::vector<bool>),您的代码可靠地停止编译。(还有其他一些小优点,比如std::addressof问题,以及它在空容器上定义明确的事实,但这些并不那么重要,尤其是在这里。)

或者,我们编写一个index_t类似迭代器的包装器:

template<class T>
struct index_t {
  T t;
  T operator*()const{ return t; }
  void operator++() { ++t; }
  friend bool operator==( index_t const& lhs, index_t const& rhs ) {
    return lhs.t == rhs.t;
  }
  friend bool operator!=( index_t const& lhs, index_t const& rhs ) {
    return lhs.t != rhs.t;
  }
};
template<class T>
index_t<T> index(T t) { return {t}; }

index_t<int>可用于创建计数for(:)循环。

index_t<iterator>可用于创建迭代器返回for(:)循环。

template<class It>
struct range_t {
  It b,e;
  It begin() const {return b;}
  It end() const {return e;}
};
template<class It>
range_t<It> range( It s, It f ) { return {s,f}; }

template<class T>
range_t<index_t<T>>
index_over( T s, T f ) {
  return {{{s}}, {{f}}};
}
template<class Container>
auto iterators_of( Container& c ) {
  using std::begin; using std::end;
  return index_over( begin(c), end(c) );
}

我们现在可以迭代容器的迭代器。

for (auto it : iterators_of(vec))

活生生的例子


提到的迭代整数是:

for (int i : index_over( 0, 100 ) )

我们也可以直接获取容器的索引:

template<class Container>
range_t< index_t<std::size_t> >
indexes_of( Container& c ) {
  return index_over( std::size_t(0), c.size() );
}
template<class T, std::size_t N>
range_t< index_t<std::size_t> >
indexes_of( T(&)[N] ) {
  return index_over( std::size_t(0), N );
}

这让我们:

for( auto i : indexes_of( vec ) )

其中i0到不等vec.size()-1。我发现这有时比 zip 迭代器等更容易使用。


省略的改进:

index_t一个真正的input_iterator. 在制作索引和范围时使用std::move和/或根据需要。std::forward支持范围内的哨兵。使range_t界面更丰富(,可选size随机访问,,,,,等。[]emptyfrontbackrange_t range_t::without_front(n) const

于 2016-10-11T15:36:43.020 回答
6

是的,这是一个有效的解决方案。基础数据保证是连续的(std::vector应该是动态数组,或多或少)。

n4140 §23.3.6.1 [vector.overview]/1

a 的元素vector是连续存储的,这意味着如果v是 avector<T, Allocator>T不是 的某个类型bool,那么它服从&v[n] == &v[0] + n所有的恒等式0 <= n < v.size()

于 2016-10-11T15:23:58.430 回答