如何在运行时选择迭代器方向



我想选择在运行时对容器进行迭代的方向,如以下示例代码所示:

#include <iostream>
#include <vector>
void iterate(bool forward, std::vector<int> v) {
auto start = v.begin();
auto end = v.end();
if (!forward) {
start = v.rbegin(); // this doesn't
end = v.rend();     // work
}
for (auto it = start; it != end; ++it) {
std::cout << *it << " -> " << *std::next(it) << std::endl;
}
std::cout << std::endl;
}
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
iterate(true, v);
iterate(false, v);
}

如何在运行时更改迭代器的方向,而不复制for循环

想象一下,循环是一个相当复杂的算法,您不想重复它以避免将来的维护问题。请注意,我需要访问循环体中的std::next/std::previor元素。

通过一点间接,即使用辅助函数,您可以调用一个模板函数,该函数接受您想要循环的迭代器类型,如

template<typename Iterator>
void iterate_helper(Iterator start, Iterator end)
{
for (auto it = start; it != end; ++it) 
{
std::cout << *it << " -> " << *std::next(it) << std::endl;
}
std::cout << std::endl;
}
void iterate(bool forward, std::vector<int> v)
{
if (!forward)
iterate_helper(v.rbegin(), v.rend());
else
iterate_helper(v.begin(), v.end());
}

如果这个示例代码是真实的代码,请注意for循环中的*std::next(it)将超过容器的末尾。您的最终条件需要在end之前停止一个才能使用它。

一些样板。一些辅助程序在c++17/c++20标准库中实现。此实现的部分内容需要c++17。

template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
bool empty() const { return begin() == end(); }
// in a real implementation, this should check if the iterators are
// random access, and if so do the simple implementation.  I don't
// want to use std::distance, because if n is small compared to the
// distance from begin to end, that could get expensive.
range_t except_last( std::size_t n = 1 ) const {
if (n==0 || empty()) return *this;
if (n==1) return { begin(), std::prev(end()) };
return except_last((n-1)/2).except_last(1+(n-1)/2);
}
};
template<class C>
auto range( C&& c ) {
using std::begin; using std::end;
return range_t{ begin(c), end(c) };
}
template<class C>
auto backwards( C&& c ) {
using std::rbegin; using std::rend;
return range_t{ rbegin(c), rend(c) };
}
template<class T>
struct index_iterator {
using difference_type = std::ptrdiff_t;
using value_type = T;
using reference = T;
T t;
T operator*() const { return t; }
T const* operator->() const { return std::addressof(t); }
index_iterator& operator++() { ++t; return *this; }
index_iterator operator++(int) { auto r = *this; ++t; r; }
friend bool operator==(index_iterator const& lhs, index_iterator const& rhs) {
return lhs.t==rhs.t;
}
friend bool operator!=(index_iterator const& lhs, index_iterator const& rhs) {
return lhs.t!=rhs.t;
}
};
template<class C>
auto iterators_into( C&& c ) {
using std::begin; using std::end;
return range_t{ index_iterator{begin(c)}, index_iterator{end(c)} };
}
template<class R, class Op>
void foreach_adjacent( R&& r, Op&& op ) {
for( auto it : iterators_into( r ).except_last() ) {
op( *it, *std::next(it) );
}
}

这是有了样板的结果:

void iterate(bool forward, std::vector<int> v) {
auto op = [](auto&& first, auto&& second) {
std::cout << first << " -> " << second << std::endl;
};
if (forward) 
foreach_adjacent( v, op ):
else
foreach_adjacent( backwards(v), op );
}

发生了什么:

range_t是使用迭代器的非拥有元素范围的包装器。

CCD_ 4和CCD_ 5基于输入容器或范围创建前向和后向CCD_。

index_iterator是一个迭代器范围内的输入迭代器。使用iterators_into,您只需使用for(:)循环在容器的一个范围内进行循环,但在每一步都会得到迭代器,而不是元素。(注意,index_iterator也可以接受std::size_t而不是iterators;这就是我通常使用它的目的。迭代器和整数都为其类型参数所需的概念建模。(

.except_lastrange_t上的一个辅助方法,用于从该范围中删除最后一个n(默认为1(元素。

foreach_adjacent对其输入的迭代器进行迭代,删除最后一个元素,然后对每个元素及其后的元素调用op(这是保证存在的,因为我们删除了最后一个元件(。

iterate函数中,我们首先编写循环的主体op,然后向前或向后访问相邻元素,调用op

在@Botje提到"多态lambdas"后,我想展示一下我的想法。我最近看了CppCon2019:Arthur O'Dwyer的《回到基础:从头开始的Lambdas》,我不太确定Arthur的"Generic Lambdas"是否是@Botje的意思,但以下代码运行良好,相当优雅IMHO:

#include <iostream>
#include <vector>
void iterate(bool forward, std::vector<int> v) {
auto l = [](auto start, auto end){
for (auto it = start; it != end; ++it) {
std::cout << *it << " -> " << *std::next(it) << std::endl;
}
};
if (forward) {
l(v.begin(), v.end());
} else {
l(v.rbegin(), v.rend());
}
std::cout << std::endl;
}
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
iterate(true, v);
iterate(false, v);
}

最新更新