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】:
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
以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库源码分析:
- 其中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;
- 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
}
};
- 调用顺序为std::views::transform(inc),再std::views::filter(odd),得到对应的_Partial对象
- 在_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));
- 而Adaptor即为:struct _Filter 和 struct _Tramsform;
- 以此类推最终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的对象的遍历;
- for (auto i : v | std::views::filter(odd) | std::views::transform(inc)) 开始遍历,首先为调用transform_view的begin获取第一个迭代器:
_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匿名函数获取到首个符合条件的迭代器;
- 对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的迭代器;
- 对filter_view::iterator取值,由于对应的迭代器在第7步中已经过滤得到,因此直接取值便可以,即对vector的迭代器取值;
- filter_view::iterator取值后再送入第8步中的*M_parent->M_fun,进行inc匿名函数的调用,获取到最终的值,至此完成一次管道处理;
详细类图即步骤如下:
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高级编程