基于矢量获取范围内的项目索引

时间:2016-10-11 15:22:18

标签: c++ c++11 ranged-loops

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是...)

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

2 个答案:

答案 0 :(得分:18)

是的,但我会改用vec.data()。使用.data()的好处是非连续的std容器没有它,因此当迭代的容器不能以这种方式工作时,您的代码可靠地停止编译(如{{1} }或deque)。 (还有其他一些小优势,例如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) );
}

live example

提到的迭代整数是:

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成为真正的index_t。在制作索引和范围时,根据需要使用input_iterator和/或std::move。支持范围内的Sentinals。使std::forward界面更丰富(range_t,可选随机访问size[]emptyfrontback

答案 1 :(得分:6)

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

  

n4140§23.3.6.1[vector.overview] / 1

     

vector的元素是连续存储的,这意味着如果vvector<T, Allocator>,其中Tbool以外的某种类型,那么遵守所有&v[n] == &v[0] + n

的身份0 <= n < v.size()