【问题标题】:Generating functors with iterator behavior生成具有迭代器行为的函子
【发布时间】:2015-03-31 12:53:11
【问题描述】:

我有一个问题,很可能以前有人问过这个问题,因为我认为我想要的是相当多的人想要的东西。但是我想不出任何表达方式来返回我想要的搜索结果(不是谷歌,不是这里)。所以也许这里的答案只是用来描述我的意思的一个术语。

我想要实现的是大致执行以下操作:

  1. 它可以采用函子结构/类并生成一系列值 基于函子的函数的所述函子。应该可以使用有状态函子,即应该可以在状态 a 中实例化函子并让它运行直到它处于状态 b,生成值范围 {f(a), f(a+1) , ..., f(b)},其中 f(a+1) 表示 f 表示的系列中的下一项。

  2. 它的行为类似于迭代器,即它可以被传递而不是迭代器,例如用值填充向量。

我认为名称应该是生成器或生成迭代器,因为它就是这样做的,但我一直没有成功找到带有该术语的东西。我已经编写了自己的实现,但它有它的问题,我想问一下,在投入更多精力之前是否有这样的事情。

为了节省您粘贴所有后续代码的工作,以防您想尝试一下,我把它放在ideone 上。我想在运行代码之后,它的作用就很清楚了。

我当前的实现看起来像这样(仅供参考,这是一个缩短的版本,其中缺少一些像 - 和 - 这样的东西,所以是的,我确实实现了它们,因此它至少可以作为双向迭代器工作。我还有一个[] 函数,所以我考虑将其设为 random_access。):

template <class F>
class generator{
public:
//typedefs required for iterator-use
typedef typename F::value_type value_type;
typedef typename F::step_type step_type;
typedef value_type * pointer;
typedef value_type & reference;
typedef typename F::size_type size_type;
typedef typename F::difference_type difference_type;
typedef std::bidirectional_iterator_tag iterator_category;

generator(value_type init, step_type step) : t(init), step(step){}

generator<F> &operator++() {
    t += step; return *this;
}

generator<F> &
operator+=(size_type n)
{
    t += n * step;
    return *this;
}

generator<F>
operator+(size_type n)
{
    return generator(*this) += n;
}

value_type operator*() const {
    return f(t);
}

value_type operator*() const {
    return f(t);
}

friend bool operator==(const generator<F> &lhs, const generator<F> &rhs){
    return lhs.t == rhs.t;
}
friend bool operator!=(const generator<F> &lhs, const generator<F> &rhs){
    return !(lhs == rhs);
}
private:
    value_type t;
    value_type step;
    F f;
};

我使用不同的模板来尽可能轻松地提供 typedef:

template <typename T>
struct simple_generator_function
{
    typedef T value_type;
    typedef T step_type;
    typedef T difference_type;
    typedef size_t size_type;
};

现在这两者与一个具体的“生成器”一起工作,如下所示:

template <typename T>
struct square_generator : public simple_generator_function<T> {

    T operator()(T t) const{
        return t * t;
    }
};

int main(void) {
    using int_sqg = generator<square_generator<int>>;
    //starting at initial value 1 take steps of size 4
    int_sqg gen(1, 1);
    //fill with all before inital + n * step
    vector<int> v(gen, gen+10);
    copy(begin(v), end(v), ostream_iterator<int>(cout, ","));
    cout << '\n';
}

长话短说:是否有 boost 或其他库,它以某种可靠的方式提供了这一点,这种迭代器/仿函数组合的一般名称是什么?

编辑 1:

我认为任何解决方案充其量只能是 InputIterator,因为据我研究,所有其他迭代器都必须从 operator*(),在这种情况下是不可能的。很可能归结为编写一个将常规函子转换为 InputIterator 的模板。

状态:到目前为止的答案都很好,但是在提问之前我已经考虑了很长时间,并且我已经考虑过类似的解决方案,所以我的问题没有得到真正的回答。我已经更新了要求 1.) 以 - 希望 - 更清楚地反映我想要的。如果没有任何结果,我可能会尝试将我当前的代码改进为更稳定的版本并将其放在 github 上。

编辑 2(赏金结束):

尽管我对解决方案并不完全满意,但 boost::irange 与 ectamur 建议的 boost::transformed 相结合最接近于做我想做的事情,所以我会给他赏金。

【问题讨论】:

  • 类似,但 function_input_iterator 似乎很难使用。我的意思是我知道我可以通过创建来创建函数,例如一个 square_gen ,我使用 bind 来修复步长并将其作为模板参数传递,但是一直使用 make_function_input_iterator 似乎很不方便。现在我可以只使用 operator+、operator- 甚至 operator[] 之类的东西,我想保持这种灵活性。
  • 顺便说一句,现在可以使用协程正确实现。我知道这个问题与人们的相关性有多大,所以如果它仍然对评论进行投票,如果它达到 5,我会详细说明,否则让这个问题不活跃并死掉。

标签: c++ iterator generator functor


【解决方案1】:

C++ 库已经提供了一些算法,这些算法可以实现您尝试自己完成的大部分功能。我认为您最好调整您的模板,以便它可以与 C++ 库无缝协作。

我特别想std::generate()

因此,您可以在第 1 步中执行您计划执行的操作,但将第 2 步替换为实现返回序列中下一个值的operator()

然后,您可以让std::generate() 负责使用您的值填充实际序列。

【讨论】:

  • 我对 std::generate() 很熟悉,显然这是我开始的地方,但是 std::generate 迭代了一些东西,而我想要一些可迭代的东西。
【解决方案2】:

当然,我们可以编写自己的迭代器:

template <typename F, typename Value>
class func_iterator
: std::iterator<
    std::random_access_iterator_tag,
    typename std::result_of<F(Value)>::type,
    Value,
    typename std::result_of<F(Value)>::type,
    typename std::result_of<F(Value)>::type>
{ .. };

这个迭代器需要三样东西:一个函数(F f)、当前值和步骤(Value value, step)。每次解引用都会计算函数的值:

using T = typename std::result_of<F(Value)>::type;
T operator*() { return f(value); }

选定的迭代函数(省略后缀,因为它们看起来相同):

func_iterator& operator++() {
    value += step;
    return *this;
}

func_iterator& operator--() {
    value -= step;
    return *this;
}

func_iterator operator+(Value amt) {
    return func_iterator{f, value + amt * step, step};
}

迭代器(对于std::distance)和相等性之间的区别:

Value operator-(const func_iterator& rhs) {
    assert(step == rhs.step);
    return (value - rhs.value) / step;
}

bool operator==(const func_iterator& rhs) {
    return value == rhs.value && step == rhs.step;
}

最后是一个为我们创建迭代器的函数:

template <typename F, typename Value>
func_iterator<F, Value> make_func_iterator(F f, Value v, Value s = 1) {
    return func_iterator<F, Value>{f, v, s};
}

把这些放在一起,我可以做这样的事情:

auto sq_it = make_func_iterator([](int x){return x*x;}, 1);
std::vector<int> squares{sq_it, sq_it + 10}; // v now holds {1, 4, 9, 16, ..., 100}

或者只是:

// get a bunch of even numbers, complicatedly:
auto ident = make_func_iterator([](int x){return x;}, 2, 2);
std::vector<int> evens{ident, ident+200}; // holds {2, 4, ..., 400}

【讨论】:

  • 是的,这行得通。你所建议的就是我现在所拥有的。但是,它存在一些问题,例如该 operator* 通常会返回除 InputIterators 之外的所有迭代器的引用。此外,它不适用于不带任何参数的函数,即无状态函数。在我最新的实现中,我已经将状态转移到函子本身,仅供参考。
  • @midor 它不必返回引用——这只是惯例——如果你这样做的话,更容易推理迭代器。它也适用于无状态函数,只需将其包装在忽略其参数的 lambda 中。
【解决方案3】:

解决这个问题的Boost.Range方法是使用transform迭代器适配器:

auto rng = boost::irange(1, 10)
    | boost::adaptors::transformed([](int i) { return i * i; });
std::vector<int> v{rng.begin(), rng.end()};

注意这如何将转换的关注点与输入范围的开始/停止/步骤(可选)参数分开。

【讨论】:

  • 与 Sam 的回答相同:此解决方案使用范围内的迭代而不是可迭代的。这是一个有效的解决方案,我一直在使用这样的东西,但这不是我想要的(第二个标准)。
  • @midor 范围可迭代的;他们支持beginend。从您的问题中我不清楚为什么只使用迭代器的解决方案更可取。
  • 是的,但是范围就是范围。从 (1,10) 开始的任何范围都会占用内存中的十个元素,您必须存储这些元素以便以后对它们应用变换。我想要的是为函子创建一个可迭代的包装器,该包装器在取消引用时进行计算并且不存储它。当你说范围是可迭代的时,你混淆了两件事:范围是可迭代的,但范围不是函数。范围是将函数应用于范围的结果。函数/函子本身不能迭代。
  • @midor 我认为您误解了 Boost.Range 的工作原理。 irange 使用常量存储;它根据需要生成结果。同样,transformed 仅在取消引用时执行计算。参见例如以coliru.stacked-crooked.com/a/ba1734654af2cc79 为例。
  • 我已经研究了 boost::irange 与适配器的结合,我认为它非常接近我想要的,但据我所知,它只能处理内置整数类型作为范围-索引,这将是我无法忍受的限制。实现这种基于整数和正整数步长的概念比我想要的要容易得多。如果我弄错了,请解释一下,因为我能在 irange 上获得的信息有些稀少。
【解决方案4】:

我想知道这是否可以称为制表。如果是这样,您对以下界面有何看法?

#include <iostream>
#include <vector>
#include <list>

#include "tabulate.hpp"

template<typename T>
void show(const T& data) {
  for(const auto & x: data) std::cout << x << " ";
  std::cout << std::endl;
}

int main() {
  auto fun = [](double x) { return 2.0 * x; };

  std::vector<double> x  {1, 2, 3, 4, 5};

  std::cout << "using range-for" << std::endl;
  for(const auto & fx : tabulate(fun, x.begin(), x.end())) {
    std::cout << fx << std::endl;
  }

  std::cout << "initializing a vector" << std::endl;
  auto init = tabulate(fun, x.begin(), x.end());
  std::vector<double> values(init.begin(), init.end());
  show(values);

  std::cout << "automatic construction of vector" << std::endl;
  auto in_vector = make_tabulation<std::vector<double>>(fun, x);
  show(in_vector);

  std::cout << "automatic construction of list" << std::endl;
  auto in_list   = make_tabulation<std::list<double>>(fun, x);
  show(in_list);
}

由以下标头实现:

#pragma once
#include <iterator>

template<typename Fun,
         typename InputIt,
         typename T = typename std::iterator_traits<InputIt>::value_type
         >
class tabulate_iterator

    : public std::iterator<std::input_iterator_tag, T> {

 public:

  tabulate_iterator()
      : m_is_valid(false) { }

  tabulate_iterator(Fun& fun, InputIt beg, InputIt end)
      : m_fun(&fun),
        m_beg(beg),
        m_end(end),
        m_is_valid(beg != end) {
    this->read();
  }

  const T& operator*() const {
    return m_current;
  }

  const T* operator->() const {
    return &(operator*());
  }

  tabulate_iterator& operator++() {
    this->read();
    return *this;
  }

  tabulate_iterator operator++(int) {
    auto tmp = *this;
    this->read();
    return tmp;
  }

  bool equals(const tabulate_iterator& other) const {
    return ((m_is_valid == other.m_is_valid) and
            (not m_is_valid));
  }

  bool operator==(const tabulate_iterator& other) const {
    return this->equals(other);
  }

  bool operator!=(const tabulate_iterator& other) const {
    return not this->equals(other);
  }

 private:

  void read() {
    if(m_is_valid and m_beg != m_end) {
      m_current = (*m_fun)(*m_beg++);
    } else {
      m_is_valid = false;
    }
  }

  T       m_current;
  Fun*    m_fun;
  InputIt m_beg;
  InputIt m_end;
  bool    m_is_valid;

};

template<typename Fun,
         typename InputIt,
         typename T = typename std::iterator_traits<InputIt>::value_type
         >
class tabulate_range {

 public:

  tabulate_iterator<Fun, InputIt, T> begin() const {
    return m_it;
  }

  tabulate_iterator<Fun, InputIt, T> end() const {
    return m_it_end;
  }

 private:

  template<typename Fun_, typename InputIt_, typename T_>
  friend tabulate_range<Fun_, InputIt_, T_> tabulate(Fun_, InputIt_, InputIt_);

  tabulate_range(Fun fun, InputIt beg, InputIt end)
      : m_it(fun, beg, end),
        m_it_end() { }

  tabulate_iterator<Fun, InputIt, T> m_it;
  tabulate_iterator<Fun, InputIt, T> m_it_end;
};

template<typename Fun,
         typename InputIt,
         typename T = typename std::iterator_traits<InputIt>::value_type
         >
tabulate_range<Fun, InputIt, T> tabulate(Fun fun, InputIt beg, InputIt end) {
  return tabulate_range<Fun, InputIt, T>(fun, beg, end);
}

template<typename OutContainer, typename Fun, typename InContainer>
OutContainer make_tabulation(Fun fun, const InContainer& x) {
  auto init = tabulate(fun, x.begin(), x.end());
  return OutContainer(init.begin(), init.end());
}

一些警告:我只是一时冲动破解了这段代码,所以很可能存在错误;将此作为概念证明。

编译(GCC 4.8.2/Linux;ICC 14.0.2 20140120/Linux):

{CXX} tabulate.cpp -std=c++11 -Wall -Wextra -Werror

输出:

$ ./a.out
using range-for
2
4
6
8
10
initializing a vector
2 4 6 8 10
automatic construction of vector
2 4 6 8 10
automatic construction of list
2 4 6 8 10

【讨论】:

  • 非常感谢,您显然在解决方案中付出了相当多的努力。有两件事我不太确定:第一,为什么你认为它应该被称为制表?其次,这似乎做了我通常用 std::transform 做的事情,因为它接受一个输入容器并通过对其应用一个函数来生成一个输出容器。整个事情背后的想法是,我不想创建输入容器,甚至可能不想创建输出容器,而只是一系列结果,即传递给的对象函数是一个命令 pat。 .
猜你喜欢
  • 2020-03-10
  • 2017-12-18
  • 1970-01-01
  • 2018-05-16
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 2015-11-07
  • 2020-07-11
相关资源
最近更新 更多