【问题标题】:Python-like loop enumeration in C++ [duplicate]C ++中类似Python的循环枚举[重复]
【发布时间】:2012-07-04 21:49:14
【问题描述】:

可能重复:
Find position of element in C++11 range-based for loop?

我有一个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)来简化最新的示例,从而使索引自包含到循环中?

【问题讨论】:

  • 为什么要简化简单的事情? :-)
  • 您必须创建一个类似生成器的函数/对象,该函数/对象返回 std::pair 并使用该对的第一个和第二个字段。您可能可以使用宏来解决问题,但是在 C++ 中没有方便而优雅的方式来使用类似 Python 的语法。您的第二个解决方案可能是最好的办法。
  • @Kos 我对解决方案 2 非常满意。只是好奇是否有更简单的方法 :)
  • 怎么样:` for ( auto x : v | boost::adaptors::indexed(0) ) { std::cout

标签: c++ for-loop c++11


【解决方案1】:

这是一种使用惰性求值的有趣解决方案。首先构造生成器对象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&amp;&amp; 分解为 index 并将 value 分解为 double&amp;

【讨论】:

  • 如果您将临时代码传递给enumerate,您的代码将严重崩溃。
  • 您使用的是哪个编译器?它不能用 g++ 4.6 或 4.7 编译。
  • @Xeo 是不是很有趣?您可能是对的,我真的不知道如何制作更安全的版本。无论如何,使用该功能甚至不如普通的旧解决方案方便。
  • 您需要根据enumerate 的参数是否为右值来更改_iter 的类型。这比看起来容易,我已经编辑了解决方案并修复了您的 beginend 函数。这是有效的,因为enumerate 中的Iterable 如果传递左值(使_iter 成员成为简单引用),将推导出为T&amp;,如果传递右值(将参数移动到@987654345),则T @ 在 ctor 中)。
  • 我刚刚发现cppitertools 的方向相似,但您可以使用a.indexa.element。请参阅枚举示例和 enumerate.hpp
【解决方案2】:

正如@Kos 所说,这是一件非常简单的事情,我真的认为没有必要进一步简化它,并且我个人会坚持使用带有索引的传统 for 循环,除了我会放弃 std::vector&lt;T&gt;::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());

【讨论】:

  • 鉴于任何接近 Python 枚举的尝试似乎最终都会导致代码膨胀,我认为最好使用这两种解决方案中的任何一种。
  • 如今,编写自定义循环已不是小事。如今,标准库算法 + range-for 循环应该可以满足您的大部分需求,如果它们不满足 - 要么您正在做一些危险的事情;或者你一直懒惰写你的容器;或者您正在编写 C++98 风格的代码。
【解决方案3】:

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

#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';
   } );
}

【讨论】:

  • IMO 这只会让事情变得更加复杂......
  • 你说得很对。通常,一个简单的 for 循环是最好的。显然 OP 不想要一个。
  • 我实际上想进一步简化代码(当然,如果可能的话)。 for idx, elem in enumerate(v): foo(idx, elem) 在我看来比我的问题或答案中发布的任何其他解决方案都简单。但是,当然,这是一个 Python 解决方案,而我要求的是一个 C++ 解决方案。
猜你喜欢
  • 2012-10-20
  • 1970-01-01
  • 2019-05-01
  • 1970-01-01
  • 2015-01-09
  • 2019-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多