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.