Подтвердить что ты не робот

Генерирование функторов с поведением итератора

У меня есть вопрос, который, скорее всего, был задан раньше, потому что я думаю, что я хочу, это то, чего захочет значительная часть людей. Однако я не мог придумать какой-либо способ выразить это, что бы вернуть то, что я хотел в поиске (не Google, не здесь). Поэтому, возможно, ответ здесь - это всего лишь один термин, используемый для описания того, что я имею в виду.

Что я хочу реализовать, это примерно то, что примерно следующее:

  • Он может принимать конструктор/класс functor и генерировать последовательность значений для упомянутого функтора, основанного на функторной функции. Должно быть возможно использовать функтор с состоянием, т.е. Должно быть возможно создать экземпляр функтора в состоянии a и запустить его до тех пор, пока он не окажется в состоянии b, генерируя диапазон значений {f (a), f (a + 1),..., f (b)}, где f (a + 1) представляет следующий элемент в ряду, представленный f.

  • Он ведет себя как итератор, то есть он может быть передан вместо итератора, например. для заполнения вектора со значениями.

Я думаю, что это имя должно быть генератором или генерировать итератор, потому что это то, что он делает, но мне очень не удалось найти что-то с этим термином. Я написал свою собственную реализацию, но у нее есть свои проблемы, и я хотел спросить, есть ли такая вещь, прежде чем прикладывать к ней какие-либо дополнительные усилия.

Чтобы сэкономить вам работу по вставке всего последующего кода на случай, если вы хотите попробовать это, я положил его на ideone. Я думаю, что после запуска кода довольно ясно, что он делает.

Моя текущая реализация выглядит так (fyi это сокращенная версия, где некоторые вещи вроде - и - отсутствуют, так что да, я их реализую, и, таким образом, она может работать хотя бы как двунаправленный итератор. [], поэтому я подумал о том, чтобы сделать его 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';
}

Короче говоря: есть ли дополнительная или другая библиотека, которая предлагает это несколько надёжным образом и как называется такой итератор /functor -mix вообще?

ИЗМЕНИТЬ 1:

Я думаю, что любое решение в лучшем случае может быть InputIterator, потому что, насколько я исследовал его, все остальные итераторы должны были бы вернуть ссылку из оператор *(), о котором в данном случае не может быть и речи. Скорее всего, дело сводится к написанию шаблона, который преобразует обычный функтор в InputIterator.

Статус: ответы до сих пор хороши, но я некоторое время думал об этом, прежде чем спрашивать, и я подумал о подобных решениях, поэтому на мой вопрос не ответил. Я обновил требование 1.) - надеюсь, - более четко отразить то, что я хочу. Если ничего из этого не произойдет, я, вероятно, попытаюсь уточнить мой текущий код в более стабильную версию и поместить ее в github.

ИЗМЕНИТЬ 2 (Конец Баунти):

Несмотря на то, что я не полностью удовлетворен решением, boost:: irange в сочетании с boost:: преобразуется, поскольку предлагаемый ectamur подходит ближе всего к тому, что я хочу, поэтому я дам ему щедрость.

4b9b3361

Ответ 1

Способ 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()};

Обратите внимание, что это отделяет проблемы преобразования от параметров start/stop/step (необязательный) диапазона ввода.

Ответ 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}

Ответ 3

Интересно, можно ли это назвать таблицей. Если да, то что вы думаете о следующем интерфейсе?

#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

Ответ 4

Библиотека С++ уже предлагает некоторые алгоритмы, которые реализуют большинство функций, которые вы пытаетесь сделать самостоятельно. Я думаю, вам лучше адаптировать ваш шаблон, чтобы он работал без проблем с библиотекой С++.

Я думаю конкретно о std::generate().

Итак, вы берете то, что планируете делать на своем шаге №1, но замените шаг №2 на реализацию operator(), который возвращает следующее значение в последовательности.

Затем вы можете позволить std::generate() заботиться о заполнении фактической последовательности вашими значениями.