ranges介绍
  ZXL8ALkrtBPG 2023年11月02日 35 0
cpp

range概念介绍

ranges为C++20引入的新特性,是对迭代器和算法库的扩展,C++ stl中的容器都可以视作一个range。

那什么是range?range是一个concept,其中concept概念可参考博文【3】中的Constraits and concepts介绍。

namespace std::ranges {
    template< class T >
    concept range = requires(T& t) {
      ranges::begin(t);
      ranges::end(t);
  };
}

标准库头文件为:<ranges>,命名空间:

namespace std {
namespace views = ranges::views;
}

range::begin返回一个起始迭代器,这里和std::begin有所区别,std::ranges::begin为一个定制点对象* (customization point object),即为一个函数对象,这个在之前的博文中有过介绍【1】;

查看源码,begin为一个定制点对象,在重载operator()是会对送入的对象进行数组、容器或者ADL判断:

struct _Begin
    {
    private:
      template<typename _Tp>
    static constexpr bool
    _S_noexcept()
    {
      if constexpr (is_array_v<remove_reference_t<_Tp>>)
        return true;
      else if constexpr (__member_begin<_Tp>)
        return noexcept(__decay_copy(std::declval<_Tp&>().begin()));
      else
        return noexcept(__decay_copy(begin(std::declval<_Tp&>())));
    }

    public:
      template<__maybe_borrowed_range _Tp>
    requires is_array_v<remove_reference_t<_Tp>> || __member_begin<_Tp>
      || __adl_begin<_Tp>
    constexpr auto
    operator()(_Tp&& __t) const noexcept(_S_noexcept<_Tp>())
    {
      if constexpr (is_array_v<remove_reference_t<_Tp>>)
        {
          static_assert(is_lvalue_reference_v<_Tp>);
          using _Up = remove_all_extents_t<remove_reference_t<_Tp>>;
          static_assert(sizeof(_Up) != 0, "not array of incomplete type");
          return __t + 0;    // 数组指针
        }
      else if constexpr (__member_begin<_Tp>)
        return __t.begin();  // stl容器,包含成员函数begin
      else
        return begin(__t);   // ADL函数调用,如果用户没有自定义则调用到std::begin默认实现
    }
    };

类似的CPO还有【2】:

ranges介绍_cpp

view

在了解了什么是range后,再看下range库中的一个重要概念——view;什么是view?

namespace std::ranges {
  template<class T>
    inline constexpr bool enable_view = derived_from<T, view_base>;
 
  template<class T>
    concept view = range<T>
                && movable<T>
                && default_initializable<T>
                && enable_view<T>;
}

view是一种特殊的range,要求支持移动构造、移动赋值、默认初始化和继承view_base;

标准库中的容器是range但不是view,而string_view是一个view;

view需要通过range工厂和range适配器产生,其本身为惰性求值:

static constexpr auto il = {3, 1, 4, 1, 5, 9}; 

    std::ranges::reverse_view rv {il}; // 此时没有进行反转
    for (int i : rv)
        std::cout << i << ' ';
    std::cout << std::endl;
    for (int i : il)
        std::cout << i << ' ';
9 5 1 4 1 3
3 1 4 1 5 9

在声明reverse_view时并没有对数组进行反转,而是在循环读取时进行反转;

std::ranges::reverse_view为一个range适配器,返回一个view

range factory

ranges介绍_cpp_02

以iota_view为例,其用于创建一个从初始值开始连续增加的序列,区间为左闭右开[start, bound)

for (int i : std::ranges::iota_view{1, 10})
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (int i : std::views::iota(1, 10))
        std::cout << i << ' ';
    std::cout << '\n';
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9

同样为延迟计算,其创造后只存储当前值,只有迭代的时候才进行增加【4】;

range adaptor

通过range适配器配合view通过管道操作符实现:

std::vector<int> v {3, 1, 4, 1, 5, 9}; 
    auto odd = [](int i){ return i % 2 != 0; };
    auto inc = [](int i){ return ++i; };
    for (auto i : v | std::views::filter(odd) | std::views::transform(inc)) {
        std::cout << i << " ";
    }
4 2 2 6 10

这种函数式串联调用,相比传统的嵌套调用具有更好的可读性;

这种函数式pipe是如何实现的?所以结合ranges库源码分析:

  1. 其中filter和transform均为可调用对象
inline constexpr _Filter filter;
inline constexpr _Transform transform;

以filter为例:

struct _Filter : __adaptor::_RangeAdaptor<_Filter>
    {
      template<viewable_range _Range, typename _Pred>
    requires __detail::__can_filter_view<_Range, _Pred>
    constexpr auto
    operator()(_Range&& __r, _Pred&& __p) const
    {
      return filter_view{std::forward<_Range>(__r), std::forward<_Pred>(__p)};
    }
        
    inline constexpr _Filter filter;
  1. std::views::filter(odd) 调用到_RangeAdaptor中的operator()
template<typename _Derived>
    struct _RangeAdaptor
    {
      // Partially apply the arguments __args to the range adaptor _Derived,
      // returning a range adaptor closure object.
      template<typename... _Args>
    requires __adaptor_partial_app_viable<_Derived, _Args...>
    constexpr auto
    operator()(_Args&&... __args) const
    {
      return _Partial<_Derived, decay_t<_Args>...>{std::forward<_Args>(__args)...};
      // _Derived即为 _Filter
    }
    };
  1. 调用顺序为std::views::transform(inc),再std::views::filter(odd),得到对应的_Partial对象
  2. 在_Partial中实现重载了operator | 管道操作:
template<typename _Adaptor, typename... _Args>
struct _Partial : _RangeAdaptorClosure {
template<typename _Self, typename _Range>
friend constexpr auto operator|(_Range&& __r, _Self&& __self)
{
    return std::forward<_Self>(__self)(std::forward<_Range>(__r));
} // __self 为 当前_Partial对象,__r为管道操作符左侧的range对象,这里vec | std::view::filter(odd),__r为vec

template<typename _Range>
constexpr auto operator()(_Range&& __r) &&
{
return _Adaptor{}(std::forward<_Range>(__r), std::move(_M_arg));
}
};

即vec | std::view::filter(odd) 返回return Adaptor{}(std::forward<Range>(_r), std::move(M_arg));

  1. 而Adaptor即为:struct _Filter 和 struct _Tramsform;
  2. 以此类推最终v | std::views::filter(odd) | std::views::transform(inc)返回一个transform_view对象
struct _Transform : __adaptor::_RangeAdaptor<_Transform> {
      template<viewable_range _Range, typename _Fp>
    requires __detail::__can_transform_view<_Range, _Fp>
    constexpr auto
    operator()(_Range&& __r, _Fp&& __f) const
    {
      return transform_view{std::forward<_Range>(__r), std::forward<_Fp>(__f)};
    }
};

至此再遍历range对象,实际时对管道中最后一个view的对象的遍历;

  1. for (auto i : v | std::views::filter(odd) | std::views::transform(inc)) 开始遍历,首先为调用transform_view的begin获取第一个迭代器:

ranges介绍_cpp_03

_M_base即为管道中的上一个view,这里为filter_view,通过ranges::begin获取filter_view中的第一个迭代器并存入到transform_view的迭代器中,查看

constexpr _Iterator begin()
{
    if (_M_cached_begin._M_has_value())
      return {this, _M_cached_begin._M_get(_M_base)};
      
    __glibcxx_assert(_M_pred.has_value());
    auto __it = __detail::find_if(ranges::begin(_M_base),
                      ranges::end(_M_base),
                      std::ref(*_M_pred));
    _M_cached_begin._M_set(_M_base, __it);
    return {this, std::move(__it)};  // 这里__it即为vector<int>的迭代器
}

可以看到在获取filter_view的迭代器对range进行过滤,即调用odd匿名函数获取到首个符合条件的迭代器;

  1. 对transform_view::iterator取值
constexpr decltype(auto) operator*() const
        noexcept(noexcept(std::__invoke(*_M_parent->_M_fun, *_M_current)))
      { return std::__invoke(*_M_parent->_M_fun, *_M_current); }

这里M_parent->M_fun即为代码中的inc 匿名函数,注意对_M_current取值,M_current即为第7步中获取的上一级的迭代器,即filter_view的迭代器;

  1. 对filter_view::iterator取值,由于对应的迭代器在第7步中已经过滤得到,因此直接取值便可以,即对vector的迭代器取值;
  2. filter_view::iterator取值后再送入第8步中的*M_parent->M_fun,进行inc匿名函数的调用,获取到最终的值,至此完成一次管道处理;

详细类图即步骤如下:

ranges介绍_cpp_04

ranges介绍_cpp_05

something else

std::sort(v1.begin(), v1.end()); // 排序
std::sort(v1.begin(), v2.end()); // 笔误导致undefined behavior

// 使用range
std::ranges::sort(v1);

除此之外,ranges还添加了对成员与成员函数的映射:

struct Info {
    int age;
    int level;
    int GetTotal() {
        return level + age;
    };  
};

std::vector<Info> v;
std::ranges::sort(v, {}, &Info::age);
std::ranges::sort(v, {}, &Info::GetTotal);

参考资料

【1】C++ADL & CPO学习总结

【2】https://en.cppreference.com/w/cpp/header/ranges

【3】模板约束介绍

【4】C++20高级编程

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  ZXL8ALkrtBPG   2023年11月02日   50   0   0 cpp
  PQYWJLBjS0G7   2023年11月02日   49   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   69   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   40   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   39   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   48   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   83   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   34   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   28   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   36   0   0 cpp
  ZXL8ALkrtBPG   2023年11月19日   18   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   58   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   70   0   0 Listcpp
  ZXL8ALkrtBPG   2023年11月02日   47   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   68   0   0 cpp
  ZXL8ALkrtBPG   2023年11月19日   31   0   0 cpp
ZXL8ALkrtBPG