Home > Mobile >  C 20 ranges custom view: Error when trying to pipe into another view
C 20 ranges custom view: Error when trying to pipe into another view

Time:10-07

I am trying to implement a custom view but getting compile time errors when trying to pipe another view into my custom view.

The code I implemented is an aggregation of this talk from CppCon2019 by Chris Di Bella and this blog post by Marius Bancila.

Below is a "minimal" reproducible code show-casing my problem:

#include <ranges>

#define FWD(value) std::forward<decltype(value)>(value)

namespace detail
{
template<typename R>
concept simple_view = std::ranges::view<R> and
                      std::ranges::range<R const> and
                      std::same_as<std::ranges::iterator_t<R>, std::ranges::iterator_t<R const>> and
                      std::same_as<std::ranges::sentinel_t<R>, std::ranges::sentinel_t<R const>>;
} // end of namespace detail

template<std::ranges::input_range R>
requires std::ranges::view<R>
class add_constant_view : std::ranges::view_interface<add_constant_view<R>>
{
public:
    using iterator_type = std::ranges::iterator_t<R>;
    using D = std::ranges::range_value_t<R>;

private:
    template<bool Const>
    class iterator;

public:
    add_constant_view() = default;
    constexpr explicit add_constant_view(R base, D val) :
        m_base(std::move(base)),
        m_val{val}
    {
    }

    constexpr auto base()   const noexcept -> R { return m_base; }
    constexpr auto constant() const noexcept -> D { return m_val; }

    constexpr auto begin()
    requires (not detail::simple_view<R>)
    {
        return iterator<false>{*this, std::ranges::begin(m_base)};
    }
    constexpr auto begin() const
    requires std::ranges::range<R const>
    {
        return iterator<true>{*this, std::ranges::begin(m_base)};
    }
    constexpr auto end()
    requires (not detail::simple_view<R>)
    {
        return iterator<false>{*this, std::ranges::end(m_base)};
    }
    constexpr auto end() const
    requires std::ranges::range<R const>
    {
        return iterator<true>{*this, std::ranges::end(m_base)};
    }

    constexpr auto size()
    requires (std::ranges::sized_range<R> and not detail::simple_view<R>)
    {
        return std::ranges::size(m_base);
    }
    constexpr auto size() const
    requires std::ranges::sized_range<R const>
    {
        return std::ranges::size(m_base);
    }


private:
    R m_base = R{};
    D m_val  = D{};

    template<bool Const>
    class iterator
    {
    private:
        template<typename R_>
        using maybe_const = std::conditional_t<Const, R_ const, R_>;

        using parent_t = maybe_const<add_constant_view<R>>;
        using base_t   = maybe_const<R>;
        friend iterator<not Const>;

        parent_t* m_parent = nullptr;
        std::ranges::iterator_t<base_t> m_current{};

        constexpr auto advance(std::ranges::range_difference_t<base_t> n) -> iterator&
        {
            if (n < 0)
                std::ranges::advance(m_current, n, std::ranges::begin(m_parent->m_base));
            else if(n > 0)
                std::ranges::advance(m_current, n, std::ranges::end(m_parent->m_base));
            return *this;
        }

    public:
        using difference_type = std::ranges::range_difference_t<base_t>;
        using value_type = std::ranges::range_difference_t<base_t>;

        using iterator_category = typename std::iterator_traits<std::ranges::iterator_t<base_t>>::iterator_category;

        iterator() = default;
        constexpr iterator(parent_t& parent,
                           std::ranges::iterator_t<base_t> it) :
            m_parent(std::addressof(parent)),
            m_current(it)
        {}

        template<bool Const_ = Const>
        requires Const and std::convertible_to<iterator_type, std::ranges::iterator_t<base_t>>
        constexpr explicit iterator(const iterator<Const_>& other) :
            m_parent(other.m_parent),
            m_current(other.m_current)
        {}

        constexpr auto base() const -> std::ranges::iterator_t<base_t>
        {
            return m_current;
        }
        constexpr auto operator* () const
        {
           return *m_current   m_parent->m_val;
        }

        constexpr auto operator   ()    -> iterator&
        requires std::ranges::forward_range<base_t>
        {
            return advance(1);
        }
        constexpr auto operator   (int) -> iterator
        requires std::ranges::forward_range<base_t>
        {
            auto tmp = *this;   *this; return tmp;
        }

        constexpr auto operator--() -> iterator&
        requires std::ranges::bidirectional_range<base_t>
        {
            return advance(-1);
        }
        constexpr auto operator-- (int) -> iterator
        requires std::ranges::bidirectional_range<base_t>
        {
            auto tmp = *this; --*this; return tmp;
        }

        constexpr auto operator =(difference_type n) -> iterator&
        requires std::ranges::random_access_range<base_t>
        {
            return advance(n);
        }

        friend
        constexpr auto operator (iterator x, difference_type n) -> iterator
        requires std::ranges::random_access_range<base_t>
        {
            return x  = n;
        }
        friend
        constexpr auto operator (difference_type n, iterator x) -> iterator
        requires std::ranges::random_access_range<base_t>
        {
            return x  = n;
        }

        constexpr auto operator-=(difference_type n) -> iterator&
        requires std::ranges::random_access_range<base_t>
        {
            return advance(-n);
        }

        friend
        constexpr auto operator-(iterator x, difference_type n) -> iterator
        requires std::ranges::random_access_range<base_t>
        {
            return x -= n;
        }
        friend
        constexpr auto operator-(difference_type n, iterator x) -> iterator
        requires std::ranges::random_access_range<base_t>
        {
            return x -= n;
        }

        friend
        constexpr auto operator-(const iterator& x, const iterator& y) -> difference_type
        {
            return std::ranges::distance(x.m_current - y.m_current);
        }


        constexpr bool operator==(const std::ranges::sentinel_t<base_t> other) const
        {
            return m_current == other;
        }

        constexpr bool operator==(const iterator& other) const
        requires std::equality_comparable<std::ranges::iterator_t<base_t>>
        {
            return m_current == other;
        }

        constexpr auto operator[](difference_type n) const -> decltype (auto)
        requires std::ranges::random_access_range<base_t>
        {
            return *(*this   n);
        }

        friend
        constexpr bool operator< (const iterator& x, const iterator& y)
        requires std::ranges::random_access_range<base_t>
        {
            return x.m_current < y.m_current;
        }
        friend
        constexpr bool operator> (const iterator& x, const iterator& y)
        requires std::ranges::random_access_range<base_t>
        {
            return y < x;
        }
        friend
        constexpr bool operator<= (const iterator& x, const iterator& y)
        requires std::ranges::random_access_range<base_t>
        {
            return not (y < x);
        }
        friend
        constexpr bool operator>= (const iterator& x, const iterator& y)
        requires std::ranges::random_access_range<base_t>
        {
            return not (x < y);
        }
        friend
        constexpr auto operator<=> (const iterator& x, const iterator& y)
        requires (std::ranges::random_access_range<base_t> and
                  std::three_way_comparable<std::ranges::iterator_t<base_t>>)
        {
            return x.m_current <=> y.m_current;
        }

        friend
        constexpr auto iter_move(const iterator& x) -> std::ranges::range_rvalue_reference_t<R>
        {
            return std::ranges::iter_move(x.m_current);
        }
        friend
        constexpr void iter_swap(const iterator& x, const iterator& y)
        requires std::indirectly_swappable<iterator>
        {
            std::ranges::iter_swap(x.m_current, y.m_current);
        }
    };

};

template<std::ranges::input_range R>
requires std::ranges::viewable_range<R>
add_constant_view(R&&, std::ranges::range_value_t<R>)
-> add_constant_view<std::ranges::views::all_t<R>>;

namespace detail
{
template<typename T>
struct add_constant_range_adaptor_closure
{
    T m_val;
    constexpr add_constant_range_adaptor_closure(T val) :
        m_val(std::move(val))
    {
    }

    template<std::ranges::viewable_range R>
    constexpr decltype(auto) operator() (R&& r) const
    {
        return add_constant_view{FWD(r), m_val};
    }
} ;

struct add_constant_range_adaptor
{
    template<std::ranges::viewable_range R>
    constexpr decltype(auto) operator() (R&& r, std::ranges::range_value_t<R> val)
    {
        return add_constant_view{FWD(r), std::move(val)};
    }

    constexpr decltype(auto) operator() (auto val)
    {
        return add_constant_range_adaptor_closure{std::move(val)};
    }
};

template<std::ranges::viewable_range R>
constexpr decltype(auto) operator| (R&& r,
                                    add_constant_range_adaptor_closure<std::ranges::range_value_t<R>>&& closure)
{
    return std::move(closure)(FWD(r));
}

With this code I can use the pipe operator like follows:

#include <vector>
#include <iostream>

int main()
{
    std::vector<int> vec = {0,1,2,3,4,5,6,7,8,9,10};
    auto view = vec | views::add_constant(2);
    for(auto&& val : view)
        std::cout << val << ",";
    std::cout << "\n";
    // prints: 2,3,4,5,6,7,8,9,10,11,12,
    return 0;
}

Now I would like to pipe another view into my view like follows:

#include <vector>
#include <iostream>

int main()
{
    std::vector<int> vec = {0,1,2,3,4,5,6,7,8,9,10};
    auto view = vec | views::add_constant(2) | std::ranges::views::reverse; // does not compile
    for(auto&& val : view)
        std::cout << val << ",";
    std::cout << "\n";
    return 0;
}

I am using GCC 10.1 and this is the compile error:

no match for 'operator|' (operand types are 'add_constant_view<std::ranges::ref_view<std::vector<int> > >' and 'const std::ranges::views::__adaptor::_RangeAdaptorClosure<std::ranges::views::<lambda(_Range&&)> >'

Here is a link to godbolt with the code: https://godbolt.org/z/hvK83GxGv

CodePudding user response:

The problem with:

auto view = vec | views::add_constant(2) | std::ranges::views::reverse;

is that in order for this to compile, vec | views::add_constant(2) needs to be a view (because it's an rvalue). If you do:

auto view = vec | views::add_constant(2);
static_assert(std::ranges::view<decltype(view)>);

you will see that this fails the enable_view part of the check, and that's because your inheritance is private:

template<std::ranges::input_range R>
requires std::ranges::view<R>
class add_constant_view : std::ranges::view_interface<add_constant_view<R>>

As such, externally it does not look like add_constant_view inherits from what it needs to inherit from. Add in public and everything works.

  • Related