std::ranges::begin 和 std::begin 有什么区别? [英] What is the difference between std::ranges::begin and std::begin?
问题描述
std::begin
和新的 std::ranges::begin
>?(end
、size
等也一样)
两者似乎工作相同:
#include <iostream>
#include <vector>
#include <array>
#include <ranges>
template<std::ranges::range R>
void printInfo(const R &range)
{
std::cout << (std::ranges::begin(range) == std::begin(range));
}
template<class T>
struct X
{
std::vector<T> v;
auto begin() const { return v.begin(); }
auto end() const { return v.end(); }
};
int main()
{
printInfo(std::vector{1, 2, 3, 4});
printInfo(std::array{1, 2, 3, 4});
printInfo(X<int>{{1, 2, 3, 4}});
int oldSchool[]{1, 2, 3, 4};
printInfo(oldSchool);
}
按预期编译并打印 1111
.
ranges::begin
是否会使 std::begin
过时?还是两者有不同的用例?
Does ranges::begin
make std::begin
obsolete? Or do the two have different use-cases?
推荐答案
有一些区别.
首先,ranges::begin(x)
适用于所有范围,而 std::begin(x)
不适用.后者不会对 begin
进行 ADL 查找,因此指定的范围如下:
First, ranges::begin(x)
works on all ranges while std::begin(x)
does not. The latter will not do ADL lookup on begin
, so ranges specified like:
struct R {
...
};
auto begin(R const&);
auto end(R const&);
行不通,这就是为什么你必须写这样的东西:
won't work, which is why you have to write something like:
using std::begin, std::end;
auto it = begin(r);
你不必用 ranges::begin
做两步.
其次,ranges::begin(x)
更安全一些.Ranges 引入了这个借用范围的概念,这是一个可以安全保留其迭代器的范围.vector
例如不是一个借用的范围 - 因为一旦 vector
死亡,数据就会死亡.ranges::begin
防止这种情况:
Second, ranges::begin(x)
is a little safer. Ranges introduces this notion of a borrowed range, which is a range whose iterators that you can hold onto safely. vector<int>
for instance is not a borrowed range - since once the vector
dies the data dies. ranges::begin
guards against that:
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
进行建模.也就是说 - 我们从 begin
和 end
得到的实际上是一个范围.
Third, ranges::begin
and ranges::end
have extra type checks. ranges::begin(r)
requires the result of either r.begin()
or begin(r)
to model input_or_output_iterator
. ranges::end(r)
requires ranges::begin(r)
to be valid and requires either r.end()
or end(r)
to model sentinel_for<decltype(ranges::begin(r))>
. That is - that whatever we get from begin
and end
is actually a range.
这意味着,例如:
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
会失败.
Although more annoyingly is a case where you have an iterator type that might be incrementable, dereferenceable, comparable, etc... but fail to have a default constructor. That does not meet the requirements of C++20's input_or_output_iterator
so ranges::begin
will fail.
四、ranges::begin
是一个函数对象,而std::begin
是一组重载的函数模板:
Fourth, ranges::begin
is a function object, while std::begin
is a set of overloaded function templates:
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
在某些情况下可以 a> 执行 ranges::end(r) - Ranges::begin(r)
(如果 size(r)
和 r.size()
不存在)就像 ranges::empty
可以 在某些情况下要么做 ranges::size(r) == 0
或 ranges::begin(r) == 范围::end(r)
.
Fifth, some of the ranges customization point objects have other fallback behavior besides just calling a function of that name. std::size(r)
always invokes a function named size
(unless r
is a raw array). std::empty(r)
always invokes a function named empty
(unless r
is a raw array, in which case it's just false
, or r
is an initializer_list
, in which case r.size() == 0
). But ranges::size
could under certain circumstances perform ranges::end(r) - ranges::begin(r)
(as a fallback if size(r)
and r.size()
don't exist) just like ranges::empty
could under certain circumstances either do ranges::size(r) == 0
or ranges::begin(r) == ranges::end(r)
.
这篇关于std::ranges::begin 和 std::begin 有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!