-
Notifications
You must be signed in to change notification settings - Fork 82
HowTo write a View add number_full
Hannes Hauswedell edited this page Feb 27, 2018
·
8 revisions
The view data structure has changed little:
#include <range/v3/all.hpp>
#include <iostream>
#include <functional>
template <typename irng_t>
// requires (bool)ranges::InputRange<irng_t>() &&
// std::is_same_v<std::decay_t<ranges::range_reference_t<irng_t>>, uint64_t>
class view_add_number
{
private:
/* data members == "the state" */
struct data_members_t
{
irng_t const & irange;
uint64_t const the_number;
};
std::shared_ptr<data_members_t> data_members;
- We have added an additional member, the number that we want to add
/* the iterator type */
struct iterator_t : ranges::iterator_t<irng_t const>
{
uint64_t the_number;
using base = ranges::iterator_t<irng_t const>;
iterator_t() = default;
iterator_t(base const & b, uint64_t const n) : base{b}, the_number{n} {}
iterator_t operator++(int)
{
return static_cast<base&>(*this)++;
}
iterator_t & operator++()
{
++static_cast<base&>(*this);
return (*this);
}
uint64_t operator*() const
{
return *static_cast<base>(*this) + the_number;
}
};
- The iterator is basically the same, but it also holds a copy of the number and add that variable when dereferenced. In cases where the iterator needs access to more parts of the view's state, it might be more practical to save a (raw) pointer to the views
data_members
public:
/* member type definitions */
using reference = uint64_t;
using const_reference = uint64_t;
using value_type = uint64_t;
using iterator = iterator_t;
using const_iterator = iterator_t;
/* constructors and deconstructors */
view_add_number() = default;
constexpr view_add_number(view_add_number const & rhs) = default;
constexpr view_add_number(view_add_number && rhs) = default;
constexpr view_add_number & operator=(view_add_number const & rhs) = default;
constexpr view_add_number & operator=(view_add_number && rhs) = default;
~view_add_number() = default;
view_add_number(irng_t const & irange, uint64_t const number)
: data_members{new data_members_t{irange, number}}
{}
/* begin and end */
iterator begin() const
{
return {std::cbegin(data_members->irange), data_members->the_number};
}
iterator cbegin() const
{
return begin();
}
iterator end() const
{
return {std::cend(data_members->irange), data_members->the_number};
}
iterator cend() const
{
return end();
}
};
- Nothing has changed here, except that constructors also take the number as second argument
namespace ranges
{
template <typename irng_t>
struct enable_view<view_add_number<irng_t>> : std::true_type
{};
} // namespace ranges::v3
static_assert((bool)ranges::InputRange<view_add_number<std::vector<uint64_t>>>());
static_assert((bool)ranges::View<view_add_number<std::vector<uint64_t>>>());
- Nothing has changed here, either.
We have some structural changes here:
struct add_number_fn
{
template <typename irng_t>
// requires (bool)ranges::InputRange<irng_t>() &&
// std::is_same_v<std::decay_t<ranges::range_reference_t<irng_t>>, uint64_t>
auto operator()(irng_t && irange, uint64_t const the_number) const
{
return view_add_number{std::forward<irng_t>(irange), the_number};
}
auto operator()(uint64_t const the_number) const
{
return std::bind(add_number_fn(), std::placeholders::_1, the_number);
}
};
template <typename irng_t>
// requires (bool)ranges::InputRange<irng_t>() &&
// std::is_same_v<std::decay_t<ranges::range_reference_t<irng_t>>, uint64_t>
auto operator|(irng_t && irange, decltype(std::bind(add_number_fn(),
std::placeholders::_1,
static_cast<uint64_t const &>(0ul))) const & bound_view)
{
return bound_view(std::forward<irng_t>(irange));
}
- First of all we need to acknowledge that we need both the input range and the number to construct our
view_add_number
, see our constructor above - The first overloaded
operator()
takes just these and easily facilitates the "functional" use of the view:
auto v = view::take(view::add_number(my_vector, 7), 3)
- but what we also want is the pipe notation:
auto v = my_vector | view::add_number(7) | view::take(3)
- To make this work we need to be able to call our generator class's
operator()
with one argument, the number, but delay the actual construction until we also get input range fromoperator|
; we do this in the secondoperator()
definition by binding the firstoperator()
(the one that takes two arguments) with the given number argument and a placeholder for input range. - We then overload the
operator|
as we did before (with the input range as left argument), but this time we take a right argument of exactly the bound type that the secondoperator()
returns! (You gotta love modern C++!) - You may have noticed that the
operator|
is no longer a friend insideadd_number_fn
but instead a free function; this is because clang requires the type definition to be complete for the call todecltype()
to succeed (GCC on the other hand can also handle a definition asfriend
). - Another clang-curiosity is the
static_cast<uint64_t const &>(0ul)
; GCC just accepts0ul
oruint64_t{}
.
This again, is the same as before:
namespace view
{
add_number_fn const add_number;
}