std::ranges::begin 和 std::begin 有什么区别?
What is the difference between std::ranges::begin and std::begin?
std::begin
and the new 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
过时吗?或者两者有不同的用例?
有一些不同。
首先,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)
安全一点。范围引入了借用范围 的概念,这是一个范围,您可以安全地保留其迭代器。 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); // 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
是一个函数对象,而std::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
可以 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 做 ranges::size(r) == 0
或 ranges::begin(r) == ranges::end(r)
.
std::begin
and the new 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
过时吗?或者两者有不同的用例?
有一些不同。
首先,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)
安全一点。范围引入了借用范围 的概念,这是一个范围,您可以安全地保留其迭代器。 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); // 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
是一个函数对象,而std::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
可以 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 做 ranges::size(r) == 0
或 ranges::begin(r) == ranges::end(r)
.