В чем разница между std :: range :: begin и 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устаревший? Или у них разные варианты использования?

1 ответ

Решение

Есть несколько отличий.

Первый, ranges::begin(x) работает на всех диапазонах, пока std::begin(x)не. Последний не будет выполнять поиск ADL наbegin, поэтому диапазоны указаны как:

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может при определенных обстоятельствах выполнитьranges::end(r) - ranges::begin(r) (в качестве запасного варианта, если size(r) а также r.size() не существует) просто как ranges::emptyможет при определенных обстоятельствах либо сделатьranges::size(r) == 0 или ranges::begin(r) == ranges::end(r).

Другие вопросы по тегам