基于向量获取范围内的项目索引
Obtaining item index in ranged based for on vector
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
是...)
ranged based for(也可能在标准中)的内部实现
是的,这是一个有效的解决方案。底层数据保证是连续的(std::vector
应该是一个动态数组,或多或少)。
n4140 §23.3.6.1 [vector.overview]/1
The elements of a vector
are stored contiguously, meaning that if v
is a vector<T, Allocator>
where T
is some type other than bool
, then it obeys the identity &v[n] == &v[0] + n
for all 0 <= n < v.size()
是的,但我会改用 vec.data()
。使用 .data()
的一个好处是非连续的 std
容器没有它,所以当被迭代的容器不能那样工作时,你的代码可靠地停止编译(比如 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) );
}
我们现在可以对容器的迭代器进行迭代。
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 ) )
其中 i
从 0
到 vec.size()-1
。我发现有时这比 zip 迭代器等更容易使用。
省略的改进:
使 index_t
成为真实的 input_iterator
。根据需要使用 std::move
and/or std::forward
创建索引和范围。在射程上支持 Sentinals。使 range_t
接口更丰富(size
,可选随机访问 []
,empty
,front
,back
,range_t range_t::without_front(n) const
,等等.
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
是...)ranged based for(也可能在标准中)的内部实现
是的,这是一个有效的解决方案。底层数据保证是连续的(std::vector
应该是一个动态数组,或多或少)。
n4140 §23.3.6.1 [vector.overview]/1
The elements of a
vector
are stored contiguously, meaning that ifv
is avector<T, Allocator>
whereT
is some type other thanbool
, then it obeys the identity&v[n] == &v[0] + n
for all0 <= n < v.size()
是的,但我会改用 vec.data()
。使用 .data()
的一个好处是非连续的 std
容器没有它,所以当被迭代的容器不能那样工作时,你的代码可靠地停止编译(比如 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) );
}
我们现在可以对容器的迭代器进行迭代。
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 ) )
其中 i
从 0
到 vec.size()-1
。我发现有时这比 zip 迭代器等更容易使用。
省略的改进:
使 index_t
成为真实的 input_iterator
。根据需要使用 std::move
and/or std::forward
创建索引和范围。在射程上支持 Sentinals。使 range_t
接口更丰富(size
,可选随机访问 []
,empty
,front
,back
,range_t range_t::without_front(n) const
,等等.