44

可能重复:
在 C++11 基于范围的 for 循环中查找元素的位置?

我有一个vector并且我想迭代它,同时可以访问每个单独元素的索引(我需​​要将元素及其索引都传递给函数)。我考虑了以下两种解决方案:

std::vector<int> v = { 10, 20, 30 };

// Solution 1
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx)
    foo(v[idx], idx);

// Solution 2
for (auto it = v.begin(); it != v.end(); ++it)
    foo(*it, it - v.begin());

我想知道是否有更紧凑的解决方案。类似于 Python 的enumerate的东西。这是我使用 C++11 范围循环得到的最接近的结果,但是必须在私有范围内定义循环外部的索引肯定似乎比 1 或 2 更糟糕的解决方案:

{
    int idx = 0;
    for (auto& elem : v)
        foo(elem, idx++);
}

有什么方法(可能使用 Boost)来简化最新的示例,使索引独立到循环中?

4

3 回答 3

28

这是一种使用惰性求值的有趣解决方案。首先,构造生成器对象enumerate_object

template<typename Iterable>
class enumerate_object
{
    private:
        Iterable _iter;
        std::size_t _size;
        decltype(std::begin(_iter)) _begin;
        const decltype(std::end(_iter)) _end;

    public:
        enumerate_object(Iterable iter):
            _iter(iter),
            _size(0),
            _begin(std::begin(iter)),
            _end(std::end(iter))
        {}

        const enumerate_object& begin() const { return *this; }
        const enumerate_object& end()   const { return *this; }

        bool operator!=(const enumerate_object&) const
        {
            return _begin != _end;
        }

        void operator++()
        {
            ++_begin;
            ++_size;
        }

        auto operator*() const
            -> std::pair<std::size_t, decltype(*_begin)>
        {
            return { _size, *_begin };
        }
};

然后,创建一个包装函数 enumerate 来推断模板参数并返回生成器:

template<typename Iterable>
auto enumerate(Iterable&& iter)
    -> enumerate_object<Iterable>
{
    return { std::forward<Iterable>(iter) };
}

您现在可以这样使用您的功能:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& a: enumerate(vec)) {
        size_t index = std::get<0>(a);
        double& value = std::get<1>(a);

        value += index;
    }
}

上面的实现只是一个玩具:它应该const与非const左值引用和右值引用一起工作,但考虑到它多次复制整个可迭代对象,对后者来说有实际成本。这个问题肯定可以通过额外的调整来解决。

从 C++17 开始,分解声明甚至允许您使用酷酷的类似 Python 的语法来直接在for初始化程序中命名索引和值:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& [index, value] : enumerate(vec)) {
        value += index;
    }
}

符合 C++ 的编译器分解auto&&推断indexasstd::size_t&&valueas double&

于 2012-07-04T12:36:27.123 回答
23

正如@Kos 所说,这是一件非常简单的事情,我真的不认为有必要进一步简化它,并且个人只会坚持使用索引的传统 for 循环,除了我会放弃std::vector<T>::size_type并简单地使用std::size_t

for(std::size_t i = 0; i < v.size(); ++i)
    foo(v[i], i);

我不太热衷于解决方案 2。它需要(有点隐藏)随机访问迭代器,这不允许您轻松交换容器,这是迭代器的强项之一。如果您想使用迭代器并使其通用(并且当迭代器不是随机访问时可能会导致性能下降),我建议使用std::distance

for(auto it(v.begin()); it != v.end(); ++it)
    foo(*it, std::distance(it, v.begin());
于 2012-07-04T11:51:38.520 回答
1

一种方法是将循环包装在您自己的函数中。

#include <iostream>
#include <vector>
#include <string>

template<typename T, typename F>
void mapWithIndex(std::vector<T> vec, F fun) {
   for(int i = 0; i < vec.size(); i++) 
       fun(vec[i], i); 
}

int main() {
   std::vector<std::string> vec = {"hello", "cup", "of", "tea"};
   mapWithIndex(vec, [](std::string s, int i){
      std::cout << i << " " << s << '\n';
   } );
}
于 2012-07-04T11:46:09.073 回答