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 n
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
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.
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.
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}