【问题标题】:Is there an equivalent to the range-based `enumerate` loop from python in modern C++?在现代 C++ 中是否有与 python 中基于范围的“枚举”循环等效的方法?
【发布时间】:2015-04-30 09:45:03
【问题描述】:

是否有等效于 C++ 中 python 的基于范围的enumerate 循环? 我会想象这样的事情。

enumerateLoop (auto counter, auto el, container) {
    charges.at(counter) = el[0];
    aa.at(counter) = el[1];
}

这可以用模板或宏来完成吗?

我知道我可以只使用老式的 for 循环并进行迭代,直到达到 container.size()。但我很感兴趣如何使用模板或宏来解决这个问题。

编辑

在 cmets 中的提示之后,我使用了 boost 迭代器。我得到了另一个使用 C++14 的可行解决方案。

template <typename... T>
auto zip(const T &... containers) -> boost::iterator_range<boost::zip_iterator<
decltype(boost::make_tuple(std::begin(containers)...))>> {
  auto zip_begin =
    boost::make_zip_iterator(boost::make_tuple(std::begin(containers)...));
  auto zip_end =
    boost::make_zip_iterator(boost::make_tuple(std::end(containers)...));
  return boost::make_iterator_range(zip_begin, zip_end);
}

template <typename T>
auto enumerate(const T &container) {
return zip(boost::counting_range(0, static_cast<int>(container.size())),
container);
} 

https://gist.github.com/kain88-de/fef962dc1c15437457a8

【问题讨论】:

  • 您想使用模板或 shudders 宏的“老派”循环究竟有什么问题?
  • 没什么。我只是想看看它是怎么做到的。

标签: c++ c++14 c++20 enumerate


【解决方案1】:

从 C 开始,多个变量的枚举一直是一种习惯用法。唯一的复杂之处是您不能在 for 循环的初始化程序中声明这两个变量。

int index;
for (auto p = container.begin(), index = 0; p != container.end(); ++p, ++index)

我认为没有比这更简单(或更强大)的了。

【讨论】:

    【解决方案2】:

    有一个 pre C++11 解决方案可以对此进行提升:boost.range.indexed。 不幸的是,它不适用于基于 C++11 范围的 for 循环,只能用于旧式详细循环。然而,对于 C++17,它应该变得(几乎)和使用 structured bindings 的 python 一样简单

    那么应该可以实现这样的工作:

    for (auto& [n,x] : enumerate(vec)) x = n;
    

    所以,还是有点等待;)

    【讨论】:

    【解决方案3】:

    前段时间我为此写了一些东西。

    本质上,您需要包装一个迭代器并为其赋予对语义。

    AFAIK,语言中没有这样的内容。而且我认为boost也没有。你几乎必须自己动手。

    // Wraps a forward-iterator to produce {value, index} pairs, similar to
    // python's enumerate()
    template <typename Iterator>
    struct EnumerateIterator {
    private:
      Iterator current;
      Iterator last;
      size_t index;
      bool atEnd;
    
    public:
      typedef decltype(*std::declval<Iterator>()) IteratorValue;
      typedef pair<IteratorValue const&, size_t> value_type;
    
      EnumerateIterator()
        : index(0), atEnd(true) {}
    
      EnumerateIterator(Iterator begin, Iterator end)
        : current(begin), last(end), index(0) {
        atEnd = current == last;
      }
    
      EnumerateIterator begin() const {
        return *this;
      }
    
      EnumerateIterator end() const {
        return EnumerateIterator();
      }
    
      EnumerateIterator operator++() {
        if (!atEnd) {
          ++current;
          ++index;
    
          atEnd = current == last;
        }
    
        return *this;
      }
    
      value_type operator*() const {
        return {*current, index};
      }
    
      bool operator==(EnumerateIterator const& rhs) const {
        return
          (atEnd && rhs.atEnd) ||
          (!atEnd && !rhs.atEnd && current == rhs.current && last == rhs.last);
      }
    
      bool operator!=(EnumerateIterator const& rhs) const {
        return !(*this == rhs);
      }
    
      explicit operator bool() const {
        return !atEnd;
      }
    };
    
    template<typename Iterable>
    EnumerateIterator<decltype(std::declval<Iterable>().begin())> enumerateIterator(Iterable& list) {
      return EnumerateIterator<decltype(std::declval<Iterable>().begin())>(list.begin(), list.end());
    }
    
    template<typename ResultContainer, typename Iterable>
    ResultContainer enumerateConstruct(Iterable&& list) {
      ResultContainer res;
      for (auto el : enumerateIterator(list))
        res.push_back(move(el));
    
      return res;
    }
    

    【讨论】:

    • 不错。我认为如果enumerate 返回一个动态生成对的范围会更好(而不是建立一个包含对的向量的完整副本)。
    • 你会想要继承 std::iterator&lt; std::input_iterator_tag, ??? &gt; 或者做一些手动的 typedefs 来成为一个完整的迭代器。我想基本的for(:) 循环不需要。
    • @JosephMansfield enumerateIterator 按照你的要求做,我想。 enumerateConstruct 只是压平它?
    • @JosephMansfield 如果您使用enumerateIterator,它具有这种行为。
    • @Yakk,是的,重新访问它并添加所有辅助函数以使其完全随机访问会很好。除了懒惰和完全缺乏需求之外,没有什么能真正阻止我。
    【解决方案4】:

    C++17 和结构化绑定使这看起来不错 - 肯定比带有本地 [i = 0](Element&amp;) mutable 的丑陋可变 lambda 或我在承认可能并非所有内容都应该硬塞到 for_each() 之前所做的任何事情要好。 等人。 - 与其他需要在for 循环之外的计数器的解决方案相比。

    for (auto [it, end, i] = std::tuple{container.cbegin(), container.cend(), 0};
         it != end; ++it, ++i)
    {
          // something that needs both `it` and `i`ndex
    }
    

    如果你经常使用这种模式,你可以把它变成通用的:

    template <typename Container>
    auto
    its_and_idx(Container&& container)
    {
        using std::begin, std::end;
        return std::tuple{begin(container), end(container), 0};
    }
    
    // ...
    
    for (auto [it, end, i] = its_and_idx(foo); it != end; ++it, ++i)
    {
        // something
    }
    

    C++ 标准提案P2164 建议添加views::enumerate,这将提供一个范围视图,为用户迭代提供元素引用和元素索引。

    我们提出了一个视图enumerate,它的值类型是struct,有两个成员index和value分别代表适应范围内元素的位置和值。

    [ . . .]

    此功能以某种形式存在于 Python、Rust、Go(支持该语言)以及许多 C++ 库中:ranges-v3、folly、boost::ranges (indexed)。

    此功能的存在或缺乏是反复出现的 stackoverflow 问题的主题。

    嘿,看!我们很有名。

    【讨论】:

      【解决方案5】:

      您还可以更优雅地使用自 C++11 以来可用的自动范围:

      int i = 0;
      for (auto& el : container){
          charges.at(counter) = el[0];
          aa.at(counter) = el[1];
          ++i;
      }
      

      不过,您仍然需要手动计算i。

      【讨论】:

      • Boost zip rangers 和计数迭代器可以使手动计数i 消失,因为 go away 的值涉及一些非常疯狂的代码。
      • @Yakk:酷,让这个答案。可能对 OP 有用。
      • @Yakk 使用 boost 我可以实现快速枚举功能。感谢您的提示。
      • 您的意思是i 而不是counter,反之亦然?无论如何,我总是觉得通过使用 mutabe lambda 来保存计数器,而不是让它泄漏到外部范围(或必须声明一个新的),这样的构造会更干净。 This answer 提到了这一点,以及在 C++17 中执行此操作的另一种更好的方法,而无需导入任何库。
      【解决方案6】:

      这是一个基于宏的解决方案,在简单性、编译时间和代码生成质量方面可能优于大多数其他解决方案:

      #include <iostream>
      
      #define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)
      
      int main() {
          fori(i, auto const & x : {"hello", "world", "!"}) {
              std::cout << i << " " << x << std::endl;
          }
      }
      

      结果:

      $ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
      0 hello
      1 world
      2 !
      

      【讨论】:

        【解决方案7】:

        Tobias Widlund 编写了一个很好的 MIT 许可 Python 样式的标头,仅枚举(虽然是 C++17):

        GitHub

        Blog Post

        真的很好用:

        std::vector<int> my_vector {1,3,3,7};
        
        for(auto [i, my_element] : en::enumerate(my_vector))
        {
            // do stuff
        }
        

        【讨论】:

          【解决方案8】:

          Boost::Range 支持这个as of 1.56。

          #include <boost/range/adaptor/indexed.hpp>
          #include <boost/assign.hpp>
          #include <iterator>
          #include <iostream>
          #include <vector>
          
          
          int main(int argc, const char* argv[])
          {
              using namespace boost::assign;
              using namespace boost::adaptors;
          
              std::vector<int> input;
              input += 10,20,30,40,50,60,70,80,90;
          
          //  for (const auto& element : index(input, 0)) // function version
              for (const auto& element : input | indexed(0))      
              {
                  std::cout << "Element = " << element.value()
                            << " Index = " << element.index()
                            << std::endl;
              }
          
              return 0;
          }
          

          【讨论】:

            猜你喜欢
            • 2014-09-01
            • 2014-01-12
            • 2014-01-14
            • 1970-01-01
            • 2011-09-23
            • 1970-01-01
            • 2011-11-02
            • 2017-04-22
            相关资源
            最近更新 更多