我已经定义了一个像这样的指针:
std::deque<BoardSquare *> mydeque;
我想在我的双端队列中使用基于范围的循环:
for (BoardSquare * b : mydeque) {
// do something involving the index of b
}
是否可以从基于循环的范围中获取项目的索引?
答案 0 :(得分:9)
不,它不是(至少不合理)。当你真的需要索引时,你根本不应该使用基于范围的for循环,而是使用一个好的旧迭代器或基于索引的for循环:
// non-idiomatic index-iteration, random access containers only
for(std::size_t i=0; i<mydeque.size(); ++i)
mydeque[i];
// awfully ugly additional iteration variable, yet generic and fast
std::size_t i = 0;
for(auto iter=mydeque.begin(); iter!=mydeque.end(); ++iter,++i)
*iter;
// idiomatic and generic, yet slow for non-random access containers
for(auto iter=mydeque.begin(); iter!=mydeque.end(); ++iter)
{
auto i = std::distance(mydeque.begin(), iter);
*iter;
}
在清晰度,惯用性,流线型和性能方面,所有这些都有其优点和缺点。
答案 1 :(得分:8)
我提出了一个解决方案 - 或者更确切地说是实验性解决方案。以下是您最终将如何使用它:
for(auto item : make_indexable(v))
{
//std::get<0>(item) is the index
//std::get<1>(item) is the object
}
这里是 minimal 实现(它只是为了演示基本思想):
#include <tuple>
#include <functional>
template<typename C>
struct indexed_container
{
struct indexed_iterator
{
typedef typename C::value_type value_type;
typedef std::tuple<size_t, std::reference_wrapper<value_type>> tuple_type;
typename C::iterator _it;
size_t _index;
indexed_iterator(typename C::iterator it) : _it(it), _index(0) {}
indexed_iterator& operator++()
{
++_it;
++_index;
return *this;
}
bool operator == (indexed_iterator const & other)
{
return _it == other._it;
}
bool operator != (indexed_iterator const & other)
{
return _it != other._it;
}
tuple_type operator*()
{
return std::make_tuple(_index, std::ref(*_it));
}
};
indexed_container(C & c) : _c(c) {}
indexed_iterator begin()
{
return indexed_iterator(_c.begin());
}
indexed_iterator end()
{
return indexed_iterator(_c.end());
}
private:
C & _c;
};
template<typename C>
auto make_indexable(C & c) -> indexed_container<C>
{
return indexed_container<C>(c);
}
测试代码:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v{1,2,3};
for(auto item : make_indexable(v))
{
std::cout << std::get<0>(item) << " => " << std::get<1>(item) << std::endl;
std::get<1>(item) *= 10; //modify value!
}
std::cout << "\nModified\n";
for(auto item : make_indexable(v))
{
std::cout << std::get<0>(item) << " => " << std::get<1>(item) << std::endl;
}
}
输出:
0 => 1
1 => 2
2 => 3
Modified
0 => 10
1 => 20
2 => 30
请注意,此解决方案并不完美,因为它不适用于temporaries和 const 容器(以及 const 对象的容器)。此外,即使您编写auto item
而不是auto & item
(实际上,您无法编写auto &item
),现在底层对象将作为引用返回。但我认为这些问题可以通过更多努力和精心设计来解决。毕竟,这只是对基本理念的一种表现。
答案 2 :(得分:3)
您需要添加一个额外的变量来跟踪索引,复制基于范围的循环使用的(不可访问的)迭代器。你需要确保它在每次迭代时都被正确初始化并递增,特别注意如果有人在循环中添加continue
语句就不会出错。
使用索引(或迭代器,在需要时可以从中计算索引)作为传统for
循环的迭代变量,会更简单,更不容易出错。
答案 3 :(得分:1)
除了@ChristianRau的优秀答案,它显示了获取循环索引的首选方法,还有一种方法可以从一个ranged-for循环中获取索引,但前提是你使用std::vector
,这是唯一保证内存中元素连续性的容器。
#include <deque>
#include <vector>
#include <iostream>
int main()
{
auto v = std::vector<int> { 0, 1, 2, 3 };
auto d = std::vector<int*> { &v[0], &v[1], &v[2], &v[3] }; // NOT: std::deque
for (auto ptr: d)
{
// assumes element contiguity, only guaranteed for std::vector!!
auto const i = std::distance(&d[0], &ptr);
std::cout << *(d[i]) << "\n";
}
}
Live output,std::deque
会崩溃。