C++ 条件单向迭代器

c++ conditional uni-directional iterator

本文关键字:迭代器 条件 C++      更新时间:2023-10-16

我想实现类似于下面的伪代码:

string foo;  // or vector<int> foo;
auto itr = bar?  foo.begin() : foo.rbegin();
auto end = bar?  foo.end() : foo.rend();
for (  ; itr != end; ++itr) {
// SomeAction ...
}

也就是说,我想将itr设置为正向迭代器或反向迭代器,具体取决于某些条件bar,以正向或反向扫描。

显然这样的代码不起作用,因为正向迭代器和反向迭代器具有不同的类型。

请注意,我不想分成两个循环,因为像// SomeAction这样的代码将被复制。

我该怎么做? 最好使用 C++11 和/或更早的答案。

另外,请详细说明字符串和矢量是否有不同的解决方案。

我会把逻辑放在一个双迭代器函数中:

<template typename Iter>
void do_stuff(Iter first, Iter last)
{
for(; first != last; ++first)
{
// Do logic
}
}
bar ? do_stuff(foo.begin(), foo.end()) : do_stuff(foo.rbegin(), foo.rend());

正向和反向迭代器对于大多数(如果不是全部)容器是不同的类型,因此不幸的是,如果是运行时决策,则不能通过使用 auto 简单地将它们分配给同一变量。

一种选择是将它们的使用转移到模板函数中:

template<class Iterator> void loop(Iterator begin, Iterator end)
{
for (auto itr = begin; itr != end; ++itr) { ... }
}
if (bar) loop(foo.begin(), foo.end());
else loop(foo.rbegin(), foo.rend());

在较新版本的 C++(C++14 及更高版本,因此不是 C++11)中,循环函数可以是 lambda,方法是使用auto作为参数类型。

auto loop = [](auto begin, auto end)
{
for (auto itr = begin; itr != end; ++itr) { ... }
};

另一种选择(尽管涉及更多)是创建一个包装器类型,该包装器类型可以包含迭代器或反向迭代器,并且至少具有比较、增量和取消引用运算符,其行为类似于迭代器本身。

我不想分成两个循环,因为像//SomeAction 这样的代码会被复制。

将操作放入 lambda 中。

auto lambda = [&](char &val) // `ElementType &`
{
// ...
};
if (bar)
{
for (auto &val : foo)
lambda(val);
}
else
{
for (auto it = foo.rbegin(); it != foo.rend(); it++)
lambda(*it);
}

或者,使用索引而不是迭代器。这仅适用于允许随机访问的容器。

std::size_t i, end, step;
if (bar)
{
i = 0;
end = foo.size();
step = 1;
}
else
{
i = foo.size() - 1;
end = -1;
step = -1;
}
for (; i != end; i += step)
{
// ...
}

一种选择是为循环编写一个适用于任何迭代器的函数模板。然后有条件地调用模板的一个实例或另一个实例。其他答案已经显示了如何做到这一点的示例。

顺便说一下,循环模板可能已经存在于<algorithm>标头中,具体取决于您正在执行的操作。您可以使用(但不限于)std::for_eachstd::accumulatestd::remove,例如:

auto body = [captures,needed,by,some,action](char c) {
// SomeAction ...
};
if (bar)
std::for_each(foo.begin(),  foo.end(),  body);
else
std::for_each(foo.rbegin(), foo.rend(), body);

如果循环的主体在此上下文之外可重用,则也可以使用命名函数,但前提是不需要捕获。使用捕获,您可以使用命名函子类型,但这涉及相当多的样板。


另一种选择是使用类型擦除迭代器适配器。它的运行时成本很小,在这里可能没有必要。但是,如果人们有一个更合适的密切相关的问题,那么提及它是有用的。

本质上,这种适配器之于模板化迭代器,就像std::function之于模板化函子参数一样。它消除了对模板的需求,这对于抽象接口特别有用。不幸的是,标准库没有提供这样的迭代器适配器。

迭代器适配器的替代是范围适配器(也不在标准库中):

using t_erase = boost::adaptors::type_erased<>;
auto range = bar
? boost::make_iterator_range(foo.begin(),  foo.end())  | t_erase()
: boost::make_iterator_range(foo.rbegin(), foo.rend()) | t_erase();
for(char c : range) {
// SomeAction ...
}
使用抽象

此功能的迭代器类。

迭代器有 3 个基本函数:

  • 增加
  • 引用
  • 检查相等性

我们可以将这些用作创建抽象此行为的接口的指南。这个接口单独使用起来有点麻烦,但我们可以使用它来构建一个包装类,GenericIterator,可以自动分配任何其他迭代器类型。

GenericIterator: 现成的通用解决方案

可以编写一个GenericIterator类,该类可以从几乎任何集合(包括反向迭代器)中分配迭代器。

int main() {
bool iterate_forward;
std::cin >> iterate_forward; 
std::vector<int> values { 1, 2, 3 };
GenericIterator<int&> begin, end; 
if(iterate_forward) {
begin = values.begin(); 
end = values.end(); 
} else {
begin = values.rbegin();
end = values.rend(); 
}
// Print out the values
for(; begin != end; ++begin) {
std::cout << *begin << " ";
}
}

您可以从此 github 存储库下载整个代码,我将根据需要对其进行更新和改进。

关于性能GenericIterator的评论

在功能方面,GenericIterator为您提供了您可能要求的一切。它重量轻;而且很方便;如果您的代码需要从std::list或矢量以外的其他内容读取,则很容易重新调整用途。

但是,由于运行时多态性的基本限制,编译器内联虚拟方法调用的难度要大得多。这意味着GenericIterator比其他解决方案承载更多的运行时开销。

如果可能,最好支持静态多态性和模板,而不是运行时多态性。如果您能够这样做,请使用类似于 Mark B 的解决方案,它最终会提高性能。

附录

迭代器接口定义。此类用于实现GenericIteratorGenericIterator包含一个指向IteratorBase的指针,用于实现运行时多态性。由于采用了clone()方法,GenericIterator仍可按预期进行复制和移动。

template <class Value>
class IteratorBase
{
public:
virtual Value         operator*() const                     = 0;
virtual IteratorBase& operator++()                          = 0;
virtual bool          operator!=(IteratorBase const&) const = 0;
virtual bool          operator==(IteratorBase const&) const = 0;
// We need this function for making copies of the iterator
virtual IteratorBase* clone() const = 0;
virtual ~IteratorBase()             = default;
};

具体类实现IteratorBase.此类实现IteratorBase中定义的行为。它包含的迭代器是要迭代的集合返回的实际迭代器。在您的情况下,要么是std::vector::iterator,要么是std::vector::reverse_iterator

template <class Iter, class Value>
class IteratorDerived : public IteratorBase<Value>
{
Iter it;
public:
IteratorDerived() = default;
IteratorDerived(Iter it) : it(it) {}
IteratorDerived(IteratorDerived const&) = default;
IteratorDerived(IteratorDerived&&)      = default;
Value                operator*() const override { return *it; }
IteratorBase<Value>& operator++() override
{
++it;
return *this;
}
bool operator!=(IteratorBase<Value> const& other) const override
{
auto* derived = dynamic_cast<IteratorDerived const*>(&other);
return derived == nullptr || it != derived->it;
}
bool operator==(IteratorBase<Value> const& other) const override
{
auto* derived = dynamic_cast<IteratorDerived const*>(&other);
return derived != nullptr && it == derived->it;
}
IteratorBase<Value>* clone() const override
{
return new IteratorDerived(*this);
}
};

GenericIterator实施。这是基于IteratorBaseIteratorDerivedGenericIterator的实际实现。任何给GenericIterator的迭代器都包装在相应的IteratorDerived中,然后分配给IteratorBase指针。

template <class Value>
class GenericIterator
{
std::unique_ptr<IteratorBase<Value>> iterator;
public:
using value_type = typename std::remove_reference<Value>::type; 
using reference = Value;
GenericIterator() = default;
GenericIterator(GenericIterator const& it) : iterator(it.iterator->clone())
{
}
GenericIterator(GenericIterator&&) = default;
// Creates a GenericIterator from an IteratorBase
explicit GenericIterator(IteratorBase<Value> const& it)
: iterator(it.clone())
{
}
// Creates a GenericIterator from an IteratorDerived
template <class Iter>
explicit GenericIterator(IteratorDerived<Iter, Value> const& it)
: iterator(it.clone())
{
}
// Creates a GenericIterator by wrapping another Iter
template <class Iter>
GenericIterator(Iter it) : iterator(new IteratorDerived<Iter, Value>(it))
{
}
GenericIterator& operator=(GenericIterator const& it)
{
iterator = std::unique_ptr<IteratorBase<Value>>(it.iterator->clone());
return *this;
}
GenericIterator& operator=(GenericIterator&&) = default;
Value            operator*() const { return *(*iterator); }
GenericIterator& operator++()
{
++(*iterator);
return *this;
}
void operator++(int) {
++(*iterator);
}
bool operator==(GenericIterator const& other) const
{
return *iterator == *other.iterator;
}
bool operator!=(GenericIterator const& other) const
{
return *iterator != *other.iterator;
}
};