有一些区别。
首先,ranges::begin(x)
适用于所有范围,std::begin(x)
而不适用。后者不会在 上进行 ADL 查找begin
,因此指定的范围如下:
struct R {
...
};
auto begin(R const&);
auto end(R const&);
不会工作,这就是为什么你必须写这样的东西:
using std::begin, std::end;
auto it = begin(r);
您不必使用ranges::begin
.
第二,ranges::begin(x)
安全一点。Ranges 引入了借用 range的概念,这是一个可以安全保存其迭代器的范围。vector<int>
例如不是借用范围-因为一旦vector
死亡,数据就会死亡。ranges::begin
防止:
auto get_data() -> std::vector<int>;
auto a = std::begin(get_data()); // ok, but now we have a dangling iterator
auto b = ranges::begin(get_data()); // ill-formed
第三,ranges::begin
有ranges::end
额外的类型检查。ranges::begin(r)
需要r.begin()
或的结果begin(r)
来建模input_or_output_iterator
。ranges::end(r)
requiresranges::begin(r)
是有效的,并且要求要么r.end()
要么end(r)
建模sentinel_for<decltype(ranges::begin(r))>
。那就是 - 无论我们得到什么begin
,end
实际上都是一个范围。
这意味着,例如:
struct X {
int begin() const { return 42; }
};
X x;
auto a = std::begin(x); // ok, a == 42
auto b = ranges::begin(x); // ill-formed, int is not an iterator
虽然更烦人的情况是,您有一个迭代器类型,它可能是可递增的、可取消引用的、可比较的等……但没有默认构造函数。这不符合 C++20 的要求,input_or_output_iterator
所以ranges::begin
会失败。
第四,ranges::begin
是一个函数对象,whilestd::begin
是一组重载的函数模板:
auto f = ranges::begin; // ok
auto g = std::begin; // error: which std::begin did you want?
第五,一些范围自定义点对象除了调用该名称的函数之外还有其他后备行为。std::size(r)
总是调用一个名为的函数size
(除非r
是原始数组)。std::empty(r)
总是调用一个名为的函数empty
(除非r
是一个原始数组,在这种情况下它只是false
,或者r
是一个initializer_list
,在这种情况下r.size() == 0
)。但是在某些情况下ranges::size
可以执行(如果不存在则作为后备)就像在某些情况下可以执行或.ranges::end(r) - ranges::begin(r)
size(r)
r.size()
ranges::empty
ranges::size(r) == 0
ranges::begin(r) == ranges::end(r)