我觉得这个问题肯定已经被问过很多次,因为在我看来这是一个非常普通的情况,但是我找不到任何指向解决方案的东西。
我正在尝试实现一个通用的可迭代Generator
对象,该对象会生成一个数字序列,直到满足某个终止条件为止为止,这表明已达到该条件以便停止迭代。
从本质上讲,基本思想是类似于Python的生成器,在该生成器中,对象会产生值,直到不再产生值为止,然后引发StopIteration
异常以通知外部循环该序列已完成
据我了解,问题分解为创建序列生成对象,然后在其上获得迭代器。
对于序列生成对象,我想我要定义一个基础Generator
类,然后将其扩展以提供特定的行为(例如,从一组范围或固定值列表中获取值)等)。所有Generaor
都会在每次调用operator()
时产生一个新值,如果生成器运行到序列末尾,则抛出ValuesFinishedException
。
我是这样实现的(我以单范围子类为例,但我需要能够对更多类型的序列进行建模):
struct ValuesFinishedException : public std::exception { };
template <typename T>
class Generator
{
public:
Generator() { };
~Generator() { };
virtual T operator()() = 0; // return the new number or raise a ValuesFinishedException
};
template <typename T>
class RangeGenerator : public Generator<T>
{
private:
T m_start;
T m_stop;
T m_step;
T m_next_val;
public:
RangeGenerator(T start, T stop, T step) :
m_start(start),
m_stop(stop),
m_step(step),
m_next_val(start)
{ }
T operator()() override
{
if (m_next_val >= m_stop)
throw ValuesFinishedException();
T retval = m_next_val;
m_next_val += m_step;
return retval;
}
void setStep(T step) { m_step = step; }
T step() { return m_step; }
};
但是,对于迭代器部分,我陷入了困境。
我已经研究过可以想到的“ Iterator”,“ Generator”和同义词的任何组合,但是我发现的只是考虑到生成器函数具有无限数量的值的情况(例如,参见boost's generator_iterator)。我曾考虑过自己编写一个Generator::iterator
类,但是我只发现了end
定义明确的琐碎迭代器(链接列表,数组重新实现)的示例。我不知道何时结束,我只知道如果我要迭代的生成器引发异常,我需要将迭代器的当前值设置为“ end()”,但是我不知道知道如何表示。
此类的原因是要有一个可以循环的灵活序列对象:
RangeGenerator gen(0.25f, 95.3f, 1.2f);
for(auto v : gen)
{
// do something with v
}
范围的示例只是最简单的一个。我将至少有三个实际用例:
对于每一个我都计划有一个Generator
子类,并为抽象Generator
定义迭代器。
答案 0 :(得分:4)
您应该使用C ++惯用法:正向迭代器。这使您可以使用C ++语法糖并支持标准库。这是一个最小的示例:
template<int tstart, int tstop, int tstep = 1>
class Range {
public:
class iterator {
int start;
int stop;
int step;
int current;
public:
iterator(int start, int stop, int step = 0, int current = tstart) : start(start), stop(stop), step(step == 0 ? (start < stop ? 1 : -1) : step), current(current) {}
iterator& operator++() {current += step; return *this;}
iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
bool operator==(iterator other) const {return std::tie(current, step, stop) == std::tie(other.current, other.step, other.stop);}
bool operator!=(iterator other) const {return !(*this == other);}
long operator*() {return current;}
// iterator traits
using difference_type = int;
using value_type = int;
using pointer = const int*;
using reference = const int&;
using iterator_category = std::forward_iterator_tag;
};
iterator begin() {return iterator{tstart, tstop, tstep};}
iterator end() {return iterator{tstart, tstop, tstep, tstop};}
};
它可以与C ++ 98方式一起使用:
using range = Range<0, 10, 2>;
auto r = range{};
for (range::iterator it = r.begin() ; it != r.end() ; ++it) {
std::cout << *it << '\n';
}
或使用新的范围循环:
for (auto n : Range<0, 10, 2>{}) {
std::cout << n << '\n';
}
与stl连接:
std::copy(std::begin(r), std::end(r), std::back_inserter(v));
答案 1 :(得分:2)
基于范围的for循环与迭代器实现begin(),end()和operator ++有关。
因此生成器必须实现它们。
template<typename T>
struct generator {
T first;
T last;
struct iterator {
using iterator_category = std::input_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T *;
using reference = T &;
T value;
iterator(T &value) : value(value) {}
iterator &operator++() {
++value;
return *this;
}
iterator operator++(int) = delete;
bool operator==(const iterator &rhs) { return value == rhs.value; }
bool operator!=(const iterator &rhs) { return !(*this == rhs); }
const reference operator *() { return value; }
const pointer operator->() const { return std::addressof(value); }
};
iterator begin() { return iterator(first); }
iterator end() { return iterator(last); }
};
然后添加一个实例化生成器的函数,就可以完成
template<typename T>
generator<T> range(T start, T end) {
return generator<T>{ start, end };
}
for (auto i : range(0, 10))
{
}
答案 2 :(得分:2)
如果您要使用最初要求的通用生成器(而不是稍后添加的更简单的用例),则可以设置以下内容 :
template <typename T>
struct Generator {
Generator() {}
explicit Generator(std::function<std::optional<T>()> f_) : f(f_), v(f()) {}
Generator(Generator<T> const &) = default;
Generator(Generator<T> &&) = default;
Generator<T>& operator=(Generator<T> const &) = default;
Generator<T>& operator=(Generator<T> &&) = default;
bool operator==(Generator<T> const &rhs) {
return (!v) && (!rhs.v); // only compare equal if both at end
}
bool operator!=(Generator<T> const &rhs) { return !(*this == rhs); }
Generator<T>& operator++() {
v = f();
return *this;
}
Generator<T> operator++(int) {
auto tmp = *this;
++*this;
return tmp;
}
// throw `std::bad_optional_access` if you try to dereference an end iterator
T const& operator*() const {
return v.value();
}
private:
std::function<std::optional<T>()> f;
std::optional<T> v;
};
如果您拥有C ++ 17(如果没有,请使用Boost或手动跟踪有效性)。很好地使用它的开始/结束功能看起来像
template <typename T>
Generator<T> generate_begin(std::function<std::optional<T>()> f) { return Generator<T>(f); }
template <typename T>
Generator<T> generate_end(std::function<std::optional<T>()>) { return Generator<T>(); }
现在有了合适的功能foo
,您可以像普通输入运算符一样使用它:
auto sum = std::accumulate(generate_begin(foo), generate_end(foo), 0);
我省略了应该在Generator
中定义的迭代器特征,因为它们在YSC的答案中-它们应该类似于以下内容(并且operator*
应该返回reference
,您应该添加operator->
,依此类推,等等。
// iterator traits
using difference_type = int;
using value_type = T;
using pointer = const T*;
using reference = const T&;
using iterator_category = std::input_iterator_tag;
答案 3 :(得分:1)
您描述的用例(范围的串联等)可能证明对库的依赖是正当的,因此这里是基于range-v3的解决方案,即将其用于C ++ 20。您可以轻松地从0到10的整数值进行迭代,步长为2,
#include <range/v3/all.hpp>
using namespace ranges;
for (auto i : view::ints(0, 11) | view::stride(2))
std::cout << i << "\n";
或使用浮点值实现类似的循环(请注意,[from,to]在此处是一个封闭范围,第三个参数表示步数)
for (auto f : view::linear_distribute(1.25f, 2.5f, 10))
std::cout << f << "\n";
当进行连接时,库开始发光:
const std::vector world{32, 119, 111, 114, 108, 100};
for (auto i : view::concat("hello", world))
std::cout << char(i);
std::cout << "\n";
请注意,以上代码段均使用-std=c++17
进行编译。该库仅是标题。