Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Tutorial: view_interface

[Note] 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] 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.


PrevUpHomeNext