std::ranges::begin 和 std::begin 有什么区别?

What is the difference between std::ranges::begin and std::begin?

std::begin and the new std::ranges::begin有什么区别? (与 endsize 等相同)

两者的工作方式似乎相同:

#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::beginranges::end 有额外的类型检查。 ranges::begin(r) 需要 r.begin()begin(r) 的结果来建模 input_or_output_iteratorranges::end(r) 要求 ranges::begin(r) 有效,并且要求 r.end()end(r) 为模型 sentinel_for<decltype(ranges::begin(r))>。也就是说 - 无论我们从 beginend 得到什么,实际上 都是一个范围。

这意味着,例如:

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 circumstancesranges::size(r) == 0ranges::begin(r) == ranges::end(r).