【问题标题】:What is the difference between std::ranges::begin and std::begin?std::ranges::begin 和 std::begin 有什么区别?
【发布时间】:2020-09-22 18:26:06
【问题描述】:

std::begin 和新的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 过时?还是两者有不同的用例?

【问题讨论】:

    标签: c++ range c++20 std-ranges


    【解决方案1】:

    有一些区别。

    首先,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&lt;int&gt; 例如 不是 借用范围 - 因为一旦 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&lt;decltype(ranges::begin(r))&gt; 建模。那就是 - 我们从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 执行ranges::end(r) - ranges::begin(r)(如果size(r)r.size() 不存在则作为后备)就像ranges::empty 可以under certain circumstances 执行ranges::size(r) == 0ranges::begin(r) == ranges::end(r)

    【讨论】:

    • 阅读范围 v3 中发生的所有幕后工作总是令人愉快的。
    • 所以ranges::begin 等确实使std::begin 过时了?还是在某些情况下仍然必须使用std::begin 而不是ranges::begin
    • 啊,好吧,我假设这样的迭代器是有效的?并且没有违反任何其他规则?
    • @cigien 取决于您如何定义“有效”。如果它们不是默认可构造的,则它们不是有效的 C++20 input_or_output_iterators。
    猜你喜欢
    • 2014-12-05
    • 2021-10-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-12
    • 1970-01-01
    • 1970-01-01
    • 2012-12-04
    • 2015-01-13
    相关资源
    最近更新 更多