在现代 C++ 中是否有等同于来自 python 的基于范围的 `enumerate` 循环?
Is there an equivalent to the range-based `enumerate` loop from python in modern C++?
在 C++ 中是否存在与来自 python 的基于范围的 enumerate
循环等效的东西?
我会想象这样的事情。
enumerateLoop (auto counter, auto el, container) {
charges.at(counter) = el[0];
aa.at(counter) = el[1];
}
这可以用模板或宏来完成吗?
我知道我可以使用老派的 for 循环并迭代直到达到 container.size()
。但我很感兴趣如何使用模板或宏来解决这个问题。
编辑
根据评论中的提示,我玩了一下 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);
}
您还可以更优雅地使用自 C++11 以来可用的自动范围:
int i = 0;
for (auto& el : container){
charges.at(counter) = el[0];
aa.at(counter) = el[1];
++i;
}
不过你还是得手数i
。
前一段时间我为此写了一些东西。
本质上,您需要包装一个迭代器并赋予它对语义。
据我所知,语言中没有内置这样的东西。而且我认为 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;
}
自 C 语言以来,枚举多个变量一直是一个习惯用法。唯一的问题是您不能在 for 循环的初始化程序中同时声明两个变量。
int index;
for (auto p = container.begin(), index = 0; p != container.end(); ++p, ++index)
我认为没有比这更简单(或更强大)的了。
有一个 C++11 之前的解决方案可以解决这个问题:boost.range.indexed。
不幸的是,它不适用于基于 C++11 范围的 for 循环,只能使用旧式的冗长循环。然而,对于 C++17,使用 structured bindings
应该变得(几乎)和 python 一样简单
那么应该可以实现像这样工作的东西:
for (auto& [n,x] : enumerate(vec)) x = n;
所以,还有一点等待 ;)
这是一个基于宏的解决方案,在简单性、编译时间和代码生成质量方面可能胜过大多数其他解决方案:
#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 !
Tobias Widlund 写了一个很好的麻省理工学院许可的 Python 样式头文件,仅枚举(尽管是 C++17):
非常好用:
std::vector<int> my_vector {1,3,3,7};
for(auto [i, my_element] : en::enumerate(my_vector))
{
// do stuff
}
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;
}
C++17 和结构化绑定使它看起来不错——当然比一些带有本地 [i = 0](Element&) mutable
的丑陋的可变 lambda 或我在承认可能不是所有东西都应该塞进 [=] 之前所做的任何事情要好13=] et al. - 而不是其他需要计数器的解决方案,其范围在 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
,这将提供一个范围的视图,为迭代它的用户提供对元素的引用和元素的索引。
We propose a view enumerate
whose value type is a struct
with 2 members index
and value
representing respectively the position and value of the elements in the adapted range.
[ . . .]
This feature exists in some form in Python, Rust, Go (backed into the language), and in many C++ libraries: ranges-v3
, folly
, boost::ranges
(indexed
).
The existence of this feature or lack thereof is the subject of recurring Whosebug questions.
嘿,看!我们出名了。
在 C++ 中是否存在与来自 python 的基于范围的 enumerate
循环等效的东西?
我会想象这样的事情。
enumerateLoop (auto counter, auto el, container) {
charges.at(counter) = el[0];
aa.at(counter) = el[1];
}
这可以用模板或宏来完成吗?
我知道我可以使用老派的 for 循环并迭代直到达到 container.size()
。但我很感兴趣如何使用模板或宏来解决这个问题。
编辑
根据评论中的提示,我玩了一下 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);
}
您还可以更优雅地使用自 C++11 以来可用的自动范围:
int i = 0;
for (auto& el : container){
charges.at(counter) = el[0];
aa.at(counter) = el[1];
++i;
}
不过你还是得手数i
。
前一段时间我为此写了一些东西。
本质上,您需要包装一个迭代器并赋予它对语义。
据我所知,语言中没有内置这样的东西。而且我认为 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;
}
自 C 语言以来,枚举多个变量一直是一个习惯用法。唯一的问题是您不能在 for 循环的初始化程序中同时声明两个变量。
int index;
for (auto p = container.begin(), index = 0; p != container.end(); ++p, ++index)
我认为没有比这更简单(或更强大)的了。
有一个 C++11 之前的解决方案可以解决这个问题:boost.range.indexed。 不幸的是,它不适用于基于 C++11 范围的 for 循环,只能使用旧式的冗长循环。然而,对于 C++17,使用 structured bindings
应该变得(几乎)和 python 一样简单那么应该可以实现像这样工作的东西:
for (auto& [n,x] : enumerate(vec)) x = n;
所以,还有一点等待 ;)
这是一个基于宏的解决方案,在简单性、编译时间和代码生成质量方面可能胜过大多数其他解决方案:
#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 !
Tobias Widlund 写了一个很好的麻省理工学院许可的 Python 样式头文件,仅枚举(尽管是 C++17):
非常好用:
std::vector<int> my_vector {1,3,3,7};
for(auto [i, my_element] : en::enumerate(my_vector))
{
// do stuff
}
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;
}
C++17 和结构化绑定使它看起来不错——当然比一些带有本地 [i = 0](Element&) mutable
的丑陋的可变 lambda 或我在承认可能不是所有东西都应该塞进 [=] 之前所做的任何事情要好13=] et al. - 而不是其他需要计数器的解决方案,其范围在 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
,这将提供一个范围的视图,为迭代它的用户提供对元素的引用和元素的索引。
We propose a view
enumerate
whose value type is astruct
with 2 membersindex
andvalue
representing respectively the position and value of the elements in the adapted range.[ . . .]
This feature exists in some form in Python, Rust, Go (backed into the language), and in many C++ libraries:
ranges-v3
,folly
,boost::ranges
(indexed
).The existence of this feature or lack thereof is the subject of recurring Whosebug questions.
嘿,看!我们出名了。