Tutorial: view_interface

| Home | Libraries | People | FAQ | More

## Tutorial: `view_interface`

| Note
All the member functions provided by `view_interface` are in your view's base class — `view_interface` — and can therefore be hidden if you define a member function with the same name in your derived view. If you don't like the semantics of any `view_interface`-provided member function, feel free to replace it.

#### The `view_interface` Template

C++20 contains a CRTP template, `std::ranges::view_interface`, which takes a range or view, and adds all the operations that view types have, using only the range's/view's `begin()` and `end()`. This is a C++14-compatible version of that template.

As with `iterator_interface`, `view_interface` makes it possible to write very few operations — only `begin()` and `end()` are actually used by `view_interface` — and get all the other operations that go with view types. The operations added depend on what kinds of iterator and/or sentinel types your derived view type defines.

Here is the declaration of `view_interface`:

    template<typename Derived, element_layout Contiguity = element_layout::discontiguous>
    struct view_interface;

`view_interface` only requires the derived type and an optional non-type template parameter that indicates whether `Derived`'s iterators are contiguous. The non-type parameter is necessary to support pre-C++20 code.

| Note
Proxy iterators are inherently discontiguous.

In this example, we're implementing something very similar to `std::ranges::drop_while_view`. First, we need helper view types `subrange` and `all_view`, and a function that takes a range and returns a view of the entire range, `all()`:

    // A subrange is simply an iterator-sentinel pair.  This one is a bit simpler
    // than the one in std::ranges; its missing a bunch of constructors, prev(),
    // next(), and advance().
    template<typename Iterator, typename Sentinel>
    struct subrange
        : boost::stl_interfaces::view_interface<subrange<Iterator, Sentinel>>
    {
        subrange() = default;
        constexpr subrange(Iterator it, Sentinel s) : first_(it), last_(s) {}

        constexpr auto begin() const { return first_; }
        constexpr auto end() const { return last_; }

    private:
        Iterator first_;
        Sentinel last_;
    };

    // std::view::all() returns one of several types, depending on what you pass
    // it.  Here, we're keeping it simple; all() always returns a subrange.
    template<typename Range>
    auto all(Range && range)
    {
        return subrange<decltype(range.begin()), decltype(range.end())>(
            range.begin(), range.end());
    }

    // A template alias that denotes the type of all(r) for some Range r.
    template<typename Range>
    using all_view = decltype(all(std::declval<Range>()));

Note that `subrange` is derived from `view_interface`, so it will have all the view-like operations that are appropriate to its `Iterator` and `Sentinel` types.

With the helpers available, we can define `drop_while_view`:

    // Perhaps its clear now why we defined subrange, all(), etc. above.
    // drop_while_view contains a view data member.  If we just took any old range
    // that was passed to drop_while_view's constructor, we'd copy the range
    // itself, which may be a std::vector.  So, we want to make a view out of
    // whatever Range we're given so that this copy of an owning range does not
    // happen.
    template<typename Range, typename Pred>
    struct drop_while_view
        : boost::stl_interfaces::view_interface<drop_while_view<Range, Pred>>
    {
        using base_type = all_view<Range>;

        drop_while_view() = default;

        constexpr drop_while_view(Range & base, Pred pred) :
            base_(all(base)),
            pred_(std::move(pred))
        {}

        constexpr base_type base() const { return base_; }
        constexpr Pred const & pred() const noexcept { return pred_; }

        // A more robust implementation should probably cache the value computed
        // by this function, so that subsequent calls can just return the cached
        // iterator.
        constexpr auto begin()
        {
            // We're forced to write this out as a raw loop, since no
            // std::-namespace algorithms accept a sentinel.
            auto first = base_.begin();
            auto const last = base_.end();
            for (; first != last; ++first) {
                if (!pred_(*first))
                    break;
            }
            return first;
        }

        constexpr auto end() { return base_.end(); }

    private:
        base_type base_;
        Pred pred_;
    };

    // Since this is a C++14 and later library, we're not using CTAD; we therefore
    // need a make-function.
    template<typename Range, typename Pred>
    auto make_drop_while_view(Range & base, Pred pred)
    {
        return drop_while_view<Range, Pred>(base, std::move(pred));
    }

Now, let's look at code using these types, including operations defined by `view_interface` that we did not have to write:

    std::vector<int> const ints = {2, 4, 3, 4, 5, 6};

    // all() returns a subrange, which is a view type containing ints.begin()
    // and ints.end().
    auto all_ints = all(ints);

    // This works using just the used-defined members of subrange: begin() and
    // end().
    assert(
        std::equal(all_ints.begin(), all_ints.end(), ints.begin(), ints.end()));

    // These are available because subrange is derived from view_interface.
    assert(all_ints[2] == 3);
    assert(all_ints.size() == 6u);

    auto even = [](int x) { return x % 2 == 0; };
    auto ints_after_even_prefix = make_drop_while_view(ints, even);

    // Available via begin()/end()...
    assert(std::equal(
        ints_after_even_prefix.begin(),
        ints_after_even_prefix.end(),
        ints.begin() + 2,
        ints.end()));

    // ... and via view_interface.
    assert(!ints_after_even_prefix.empty());
    assert(ints_after_even_prefix[2] == 5);
    assert(ints_after_even_prefix.back() == 6);

If you want more details on `view_interface`, you can find it wherever you usually find reference documentation on the standard library. We won't cover it here for that reason. See [view.interface on eel.is] or https://cppreference.com for details.