C++中类似Python的循环枚举

bet*_*ido 28 c++ for-loop c++11

可能重复:
在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());
Run Code Online (Sandbox Code Playgroud)

我想知道是否有更紧凑的解决方案.与Python的枚举类似的东西.这是我使用C++ 11范围循环时最接近的,但是必须在私有范围内定义循环外的索引,这似乎比1或2更糟糕的解决方案:

{
    int idx = 0;
    for (auto& elem : v)
        foo(elem, idx++);
}
Run Code Online (Sandbox Code Playgroud)

是否有任何方法(可能使用Boost)以这样的方式简化最新的示例,使索引自包含到循环中?

Mor*_*enn 19

这是一种使用懒惰评估的有趣解决方案.首先,构造生成器对象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 };
        }
};
Run Code Online (Sandbox Code Playgroud)

然后,创建一个包装函数枚举,它将推导出模板参数并返回生成器:

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的实现仅仅是一个玩具:它应该同时使用const和非const左值引用以及右值引用,但是后者有实际成本,因为它会多次复制整个可迭代对象.通过额外的调整肯定可以解决这个问题.

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

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

我手边没有符合C++ 17标准的编译器来检查它,但我希望auto&&分解中能够推断出indexas std::size_tvalueas double&.

  • 您需要根据`enumerate`的参数是否为rvalue来改变`_iter`的类型.这比看起来更容易,我编写了解决方案并修复了`begin`和`end`函数.这是有效的,因为`enumerate`中的`Iterable'将被推导为`T&`如果你传递一个左值(使`_iter`成员成为一个简单的引用),如果你传递一个rvalue就把它推到`T`(将参数移动到`_iter `在ctor中). (2认同)
  • 我刚刚发现 [cppitertools](https://github.com/ryanhaining/cppitertools) 的方向类似,但你会使用 `a.index` 和 `a.element`。请参阅枚举示例和 enumerate.hpp (2认同)

Xeo*_*Xeo 14

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

for(std::size_t i = 0; i < v.size(); ++i)
    foo(v[i], i);
Run Code Online (Sandbox Code Playgroud)

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

for(auto it(v.begin()); it != v.end(); ++it)
    foo(*it, std::distance(it, v.begin());
Run Code Online (Sandbox Code Playgroud)

  • 鉴于任何接近Python枚举的任何尝试似乎都会导致巨大的代码膨胀,我认为最好只使用这两种解决方案中的任何一种. (3认同)
  • 如今,编写自定义循环已不是小事。如今,标准库算法 + range-for 循环应该可以处理您需要的大部分内容,如果没有 - 要么您正在做一些危险的事情;或者您在编写容器时很懒惰;或者您正在编写 C++98 风格的代码。 (2认同)