В чем разница между 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)
.