问题
I have a question, which very likely has been asked like this before, because I think what I want is something that a considerable amount of people would want. However I could not come up with any way of expressing it that would return what I wanted in search (not google, not here). So maybe the answer here is just a single term used to describe what I mean.
What I want to implement is something that roughly does the following:
It can take a functor struct/class and generate a sequence of values for said functor based on the functor's function. It should be possible to use a stateful functor, i.e. it should be possible to instantiate a functor in state a and let it run until it is in state b, generating the range of values {f(a), f(a+1), ..., f(b)}, where f(a+1) represents the next item in the series represented by f.
It behaves like an iterator, i.e. it can be passed instead of an iterator e.g. to fill a vector with values.
I think the name should be generator or generating iterator, because that's what it does, but I have been very unsuccessful in finding something with that term. I have written my own implementation, but it has its issues, and I wanted to ask, if there is such a thing before putting any more effort into it.
To save you the work of pasting all the subsequent code in case you want to try this out I put it on ideone. I think after running the code it is pretty clear what it does.
My current implementation looks like this (fyi this is a shortened version, where some stuff like -- and - are missing, so yes I do implement them, and thus it can work at least as bidirectional iterator. I also have a [] function, so I thought about making it 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;
};
I use a different template to provide the typedefs as easily as possible:
template <typename T>
struct simple_generator_function
{
typedef T value_type;
typedef T step_type;
typedef T difference_type;
typedef size_t size_type;
};
Right now these two work together with a concrete "generator" like this:
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';
}
Long story short: Is there a boost or other library, which offers this in a somewhat reliable manner and what is the name of such a iterator/functor-mix in general?
EDIT 1:
I think any solution can at best be an InputIterator, because as far as I have researched it, all other iterators would have to return a reference from operator*(), which is out of the question in this case. Very likely, it comes down to writing a template that converts a regular functor into an InputIterator.
Status: the answers so far are good, but I have put some thought into this for quite a while before asking, and I had thought about similar solutions, so my question is not really answered. I have updated requirement 1.) to - hopefully - reflect more clearly what I want. If nothing comes of this I will probably try to refine my current code into a more stable version and put it on github.
EDIT 2 (End of Bounty):
Even though I am not entirely satisfied with the solution, boost::irange in combination with boost::transformed as ectamur suggested come closest to doing what I want, so I will give the bounty to him.
回答1:
The Boost.Range way to solve this problem is to use the transform
iterator adaptor:
auto rng = boost::irange(1, 10)
| boost::adaptors::transformed([](int i) { return i * i; });
std::vector<int> v{rng.begin(), rng.end()};
Note how this separates the concerns of the transformation from the start/stop/step (optional) parameters of the input range.
回答2:
Sure, we can just write our own iterator:
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>
{ .. };
This iterator needs three things: a function (F f
), the current value and the step (Value value, step
). Dereferencing will calculate the value of the function each time:
using T = typename std::result_of<F(Value)>::type;
T operator*() { return f(value); }
Selected iterating functions (omitting postfix since they look the same):
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};
}
Difference between iterators (for std::distance
) and equality:
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;
}
And finally a function to make an iterator for us:
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};
}
Putting that together, I can do something like:
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}
Or just:
// 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:
I wonder if this could be called tabulation. If so, what do you thing about the following interface?
#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);
}
Which is implemented by the following header:
#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());
}
Some caveats: I just cracked this code in the spur of the moment, so bugs are likely; take this as a proof of concept.
Compilation (GCC 4.8.2/Linux; ICC 14.0.2 20140120/Linux):
{CXX} tabulate.cpp -std=c++11 -Wall -Wextra -Werror
Output:
$ ./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:
The C++ library already offers some algorithms that implement most of the functionality you're trying to do on your own. I think it's better for you to adapt your template so that it would work seamlessly with the C++ library, instead.
I'm thinking specifically of std::generate()
.
So, you take what you're planning to do in your step #1, but replace step #2 with implementing an operator()
that returns the next value in the sequence.
Then, you can let std::generate
() take care of populating an actual sequence with your values.
来源:https://stackoverflow.com/questions/28263134/generating-functors-with-iterator-behavior