3

我已经定义了一个这样的指针双端队列:

std::deque<BoardSquare *> mydeque;

我想在我的双端队列中使用基于范围的循环:

for (BoardSquare * b : mydeque) {

    // do something involving the index of b
}

是否可以从基于 for 循环的范围内获取项目的索引?

4

4 回答 4

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

尽管如此,所有这些在清晰度、惯用性、流线型和性能方面都有其优点和缺点。

于 2013-07-19T15:42:17.690 回答
8

我想出了一个解决方案——或者更确切地说是一个实验性的解决方案。以下是您最终将如何使用它:

for(auto item : make_indexable(v))
{
      //std::get<0>(item) is the index
      //std::get<1>(item) is the object
}

这是最小的实现(这只是为了展示基本思想):

#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

在线演示

请注意,此解决方案并不完美,因为它不适用于临时容器和const容器(以及const对象的容器)。此外,即使您写而不是(实际上,您不能写),现在底层对象也将作为引用返回。但我认为这些问题可以通过更多的努力和仔细的设计来解决。毕竟,这只是基本思想的演示。auto itemauto & itemauto &item

于 2013-07-19T16:18:59.207 回答
3

您需要添加一个额外的变量来跟踪索引,复制基于范围的循环使用的(不可访问的)迭代器。您需要确保它在每次迭代中正确初始化和递增,特别注意如果有人将continue语句添加到循环中它不会出错。

for使用索引(或迭代器,您可以在需要时从中计算索引)作为常规循环的迭代变量会更简单且不易出错。

于 2013-07-19T15:45:55.623 回答
2

除了 @ChristianRau 的出色回答(它显示了获取循环索引的首选方法)之外,还有一种方法可以从 ranged-for 循环中获取索引,但前提是您使用 a 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";
    }
}

现场输出,必死机std::deque

于 2013-07-19T18:44:27.397 回答