0

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

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.