有一些区别。
首先,ranges::begin(x)
适用于所有范围,而 std::begin(x)
不是。后者不会在 begin
上进行 ADL 查找,因此指定为以下范围时:
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引入了一个名为“借用范围”的概念,它是一个可以安全地持有迭代器的范围。例如,
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)
要求
ranges::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);
auto b = ranges::begin(x);
有时候你可能拥有一个迭代器类型,这个类型可以进行递增、解引用、比较等操作,但是却没有默认构造函数。这不符合C++20中input_or_output_iterator
的要求,因此ranges::begin
会失败。
第四个区别是,ranges::begin
是一个函数对象,而std::begin
是一组重载的函数模板:
auto f = ranges::begin
auto g = std::begin
第五点,一些范围自定义点对象除了调用该名称函数之外,还有其他的回退行为。
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)
一样。
ranges::begin
等是否使得std::begin
已经过时?或者在什么情况下还必须使用std::begin
而不是ranges::begin
? - cigienranges::begin
会因为这些额外的类型检查而拒绝它们。 - Barryinput_or_output_iterator
。 - Barrystd::begin
,对吗? 但是也许我们可以更进一步,从不使用.begin()
,因为ranges::begin
具有借用范围检查,或者这样做会很傻?(另外不幸的是,它要冗长得多;这是 C++ 有时似乎“擅长”的事情) - SWdV