c++ - 范围修剪 View 实现不适用于反向 View

标签 c++ stl c++20 range-v3

我编写了一个名为trim的C++ 20范围 View (不是range-v3 View ),给定了一个范围和一个一元谓词,它返回了一个新的范围,而没有满足该谓词的前后元素。 (range-v3库具有这样的 View ,但是C++ 20中缺少它。)

这是实现(这可能不是最佳方法,但是Ranges库上没有很多文档,因此在资源有限的情况下,这是我能想到的):

namespace rg = std::ranges;

// -------- trim ----------

template<rg::input_range R, typename P> requires rg::view<R>
class trim_view : public rg::view_interface<trim_view<R, P>>
{
private:
    R base_ {};
    P pred_;
    mutable rg::iterator_t<R> iter_ {std::begin(base_)};
    mutable rg::iterator_t<R> end_  {std::end(base_)};
public:
    trim_view() = default;

    constexpr trim_view(R base, P pred)
        : base_(std::move(base)), pred_(std::move(pred)), iter_(std::begin(base_)), end_(std::end(base_))
    {}

    constexpr R base() const &
    {return base_;}
    constexpr R base() && 
    {return std::move(base_);}

    constexpr auto begin()
    {
        while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
        while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
        return iter_;
    }
    constexpr auto begin() const requires rg::range<const R>
    {
        while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
        while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
        return iter_;
    }
    constexpr auto begin() requires rg::random_access_range<R> && rg::sized_range<R>
    {
        while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
        while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
        return iter_;
    }
    constexpr auto begin() const requires rg::random_access_range<const R> && rg::sized_range<const R>
    {
        while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
        while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
        return iter_;
    }

    constexpr auto end()
    { return end_ ; }
    constexpr auto end() const requires rg::range<const R>
    { return end_ ; }
    constexpr auto end() requires rg::random_access_range<R> && rg::sized_range<R>
    { return end_ ; }
    constexpr auto end() const requires rg::random_access_range<const R> && rg::sized_range<const R>
    { return end_ ; }

    constexpr auto size() requires rg::sized_range<R>
    { return std::distance(iter_, end_); }    
    constexpr auto size() const requires rg::sized_range<const R>
    { return std::distance(iter_, end_); }
};

template<class R, typename P>
trim_view(R&& base, P pred)
    -> trim_view<rg::views::all_t<R>, P>;

namespace details
{
    template <typename P>
    struct trim_view_range_adaptor_closure
    {
        P pred_;
        constexpr trim_view_range_adaptor_closure(P pred)
            : pred_(pred)
        {}

        template <rg::viewable_range R>
        constexpr auto operator()(R && r) const
        {
            return trim_view(std::forward<R>(r), pred_);
        }
    } ;

    struct trim_view_range_adaptor
    {
        template<rg::viewable_range R, typename P>
        constexpr auto operator () (R && r, P pred)
        {
            return trim_view( std::forward<R>(r), pred ) ;
        }

        template <typename P>
        constexpr auto operator () (P pred)
        {
            return trim_view_range_adaptor_closure(pred);
        }   
    };

    template <rg::viewable_range R, typename P>
    constexpr auto operator | (R&& r, trim_view_range_adaptor_closure<P> const & a)
    {
        return a(std::forward<R>(r)) ;
    }
}

namespace views
{
    inline static details::trim_view_range_adaptor trim;
}

它工作正常。我写了一些测试以确保一切正常。
template <typename P>
void are_equal(std::vector<int> const & input, std::vector<int> const & output, P&& pred)
{
    std::size_t index = 0;
    for(auto  i : input | views::trim(std::forward<P>(pred)))
    {
        assert(i == output[index]);
        index++;
    }
    assert(index == output.size());
}

int main()
{
    auto is_odd = [](const int x){return x%2==1;};

    are_equal({}, {}, is_odd);
    are_equal({1}, {}, is_odd);    
    are_equal({1,3,5}, {}, is_odd);
    are_equal({2}, {2}, is_odd);
    are_equal({2,4}, {2,4}, is_odd);
    are_equal({2,3,4}, {2,3,4}, is_odd);
    are_equal({1,2,3,4}, {2,3,4}, is_odd);
    are_equal({1,1,2,3,4}, {2,3,4}, is_odd);
    are_equal({2,3,4,5}, {2,3,4}, is_odd);
    are_equal({2,3,4,5,5}, {2,3,4}, is_odd);
    are_equal({1,2,3,4,5}, {2,3,4}, is_odd);
    are_equal({1,1,2,3,4,5,5}, {2,3,4}, is_odd);
}

问题是,当我在修剪后应用views::reverse View 时,它将不再正常工作。
template <typename P>
void are_equal_reverse2(std::vector<int> const & input, std::vector<int> const & output, P&& pred)
{
    std::size_t index = 0;
    for(auto  i : input | views::trim(std::forward<P>(pred)) | rg::views::reverse)
    {
        assert(i == output[index]);
        index++;
    }
    assert(index == output.size());
}

int main()
{
    auto is_odd = [](const int x){return x%2==1;};

    // OK
    are_equal_reverse2({}, {}, is_odd);
    are_equal_reverse2({1}, {}, is_odd);
    are_equal_reverse2({1,3,5}, {}, is_odd);
    are_equal_reverse2({2}, {2}, is_odd);
    are_equal_reverse2({2,4}, {4,2}, is_odd);
    are_equal_reverse2({2,3,4}, {4,3,2}, is_odd);
    are_equal_reverse2({1,2,3,4}, {4,3,2}, is_odd);
    are_equal_reverse2({1,1,2,3,4}, {4,3,2}, is_odd);

    // fail
    are_equal_reverse2({2,3,4,5}, {4,3,2}, is_odd);
    are_equal_reverse2({2,3,4,5,5}, {4,3,2}, is_odd);
    are_equal_reverse2({1,2,3,4,5}, {4,3,2}, is_odd);
    are_equal_reverse2({1,1,2,3,4,5,5}, {4,3,2}, is_odd);
}

范围{2,3,4,5}变为{2,3,4}。启用反向后,它将变为{4,3,2}。但是,结果实际上是{5,4,3,2}。

我希望views::reverse在修剪 View 的开始和结束迭代器上应用std::make_reverse_iterator()。那应该执行以下转换:
trim_view        reverse_view (expected)      reverse_view (actual)
--------------------------------------------------------------------
2 3 4 5 _         _ 2 3 4 5                   _ 2 3 4 5
^     ^           ^     ^                     ^       ^
|     |      =>   |     |                     |       |
|     end_        rend  |                     rend    |
iter_                   rbegin                        rbegin

我不确定我在这里缺少什么。任何帮助表示赞赏。

以下是工作示例的链接:https://wandbox.org/permlink/4iFNsqiz9Y9Bfm64

最佳答案

首先,让我们开始:

constexpr auto begin()
{
    while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
    while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
    return iter_;
}
constexpr auto begin() const requires rg::range<const R>
{
    while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
    while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
    return iter_;
}
constexpr auto begin() requires rg::random_access_range<R> && rg::sized_range<R>
{
    while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
    while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
    return iter_;
}
constexpr auto begin() const requires rg::random_access_range<const R> && rg::sized_range<const R>
{
    while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
    while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
    return iter_;
}

constexpr auto end()
{ return end_ ; }
constexpr auto end() const requires rg::range<const R>
{ return end_ ; }
constexpr auto end() requires rg::random_access_range<R> && rg::sized_range<R>
{ return end_ ; }
constexpr auto end() const requires rg::random_access_range<const R> && rg::sized_range<const R>
{ return end_ ; }

所有这些重载都执行完全相同的操作,因此我们可以减少到两个:
constexpr auto begin() const
{
    while(iter_ != std::end(base_) && pred_(*iter_)) {iter_ = std::next(iter_);} 
    while(end_ != iter_ && pred_(*std::prev(end_))) {end_ = std::prev(end_);}
    return iter_;
}

constexpr auto end() const
{ return end_ ; }

好吧这里发生了什么? begin()将调整修剪iter_end_,而end()仅返回end_

很好,如果您这样做:
auto trimmed = some_range | trim(some_pred);
auto b = trimmed.begin();
auto e = trimmed.end();

但是,如果执行此操作会发生什么:
auto e = trimmed.end();
auto b = trimmed.begin();

在这种情况下,end将是some_range.end(),它将不是该范围的正确的结束迭代器!您需要确保begin()end()之间没有任何顺序依赖性-它们始终必须返回正确的值。

同样,trim(p)可以整体减少为:
template <rg::viewable_range R, typename P>
constexpr auto operator ()(R && r, P pred)
{
    auto negated = std::not_fn(pred);
    auto f = rg::find_if(r, negated);
    auto l = rg::find_if(r | std::views::reverse, negated).base();
    return rg::subrange(f, l);
}

关于c++ - 范围修剪 View 实现不适用于反向 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62199348/

相关文章:

c++ - 是否可以禁止引用对象

c++ - 即使只有主函数,在主函数外部声明变量也会更改输出

c++ - 一个不适用于类的属性

c++ - 为什么 views::reverse 可以将 non-sized_range 转换为 size_range?

c++ - 分配器感知 `std::array` 风格的容器?

c++ - 编译时系数和值序列的内积

c++ - Lua:加载第二个字符串后无法获取字段;

c++ - 两个序列与 STL 的匹配数

c++ - 我什么时候通过 const& std::string 而不是 std::string_view ?

c++ - 如何调整集合迭代器使其表现得像 map 迭代器?