Skip to content

HowTo write a View add number_full

Hannes Hauswedell edited this page Feb 27, 2018 · 8 revisions

view_add_number

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.

add_number_fn

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 from operator|; we do this in the second operator() definition by binding the first operator() (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 second operator() returns! (You gotta love modern C++!)
  • You may have noticed that the operator| is no longer a friend inside add_number_fn but instead a free function; this is because clang requires the type definition to be complete for the call to decltype() to succeed (GCC on the other hand can also handle a definition as friend).
  • Another clang-curiosity is the static_cast<uint64_t const &>(0ul); GCC just accepts 0ul or uint64_t{}.

view::add_number

This again, is the same as before:

namespace view
{

add_number_fn const add_number;

}
Clone this wiki locally