C++20 范围自定义视图:尝试通过管道传输到另一个视图时出错

C++20 ranges custom view: Error when trying to pipe into another view

我正在尝试实现自定义视图,但在尝试将另一个视图通过管道传输到我的自定义视图时出现编译时错误。

我实现的代码是 Marius Bancila this talk from CppCon2019 by Chris Di Bella and this 博客 post 的集合。

下面是展示我的问题的“最小”可重现代码:

#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));
}

通过这段代码,我可以像下面这样使用管道运算符:

#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;
}

现在我想将另一个视图通过管道传输到我的视图中,如下所示:

#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;
}

我正在使用 GCC 10.1,这是编译错误:

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&&)> >'

这里有一个 link 代码:https://godbolt.org/z/hvK83GxGv

问题:

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

是为了编译,vec | views::add_constant(2) 需要是一个 view (因为它是一个右值)。如果你这样做:

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

您会看到这未通过检查的 enable_view 部分,那是因为您的继承是 private:

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

因此,从外部看它并不像 add_constant_view 继承了它需要继承的东西。添加 public 一切正常。