Почему std::get имеет только две перегрузки функций для диапазонов::поддиапазона?

#c #c 20 #std-ranges

Вопрос:

В стандарте существует четыре парных типа, а именно std::array , std::pair , std::tuple , и ranges::subrange , где перегрузка std::get для ranges::subrange определена в [диапазон.поддиапазон#доступ-10]:

 template<size_t N, class I, class S, subrange_kind K>
  requires (N < 2)
  constexpr auto get(const subrange<I, S, K>amp; r);
template<size_t N, class I, class S, subrange_kind K>
  requires (N < 2)
  constexpr auto get(subrange<I, S, K>amp;amp; r);
 

Эффекты: Эквивалентны:

 if constexpr (N == 0)
  return r.begin(); 
else
  return r.end();
 

И ranges::subrange::begin() имеет две перегрузки:

 constexpr I begin() const requires copyable<I>;
[[nodiscard]] constexpr I begin() requires (!copyable<I>);
 

Я заметил, что это std::get имеет только две перегрузки, и нет соответствующей перегрузки для ссылки на значение, не являющееся постоянным, что делает невозможным для нас обращаться std::get к input_range значению с помощью copyable неитератора (godbolt):

 #include <ranges>
#include <sstream>

int main() {
  auto ints = std::istringstream{"42"};
  auto is = std::ranges::istream_view<int>(ints);
  std::ranges::input_range auto r = std::ranges::subrange(is);
  auto b  = r.begin();      // OK
  auto b2 = std::get<0>(r); // Error, passing 'const subrange' as 'this' argument discards qualifiers
}
 

Так почему же std::get существует только две перегрузки функций для ranges::subrange ? Пропускает ли он перегрузки для ranges::subrangeamp; и const ranges::subrangeamp;amp; ? Это стандартный дефект или он преднамеренный?

Ответ №1:

Как правило, полностью сформированный subrange в четко определенном коде представляет допустимый диапазон, и если в нем хранится размер, то размер равен размеру диапазона. Это отражено в предварительном условии каждого конструктора, не используемого по умолчанию (созданное по умолчанию состояние все еще может быть частично сформировано). Это немного запуталось из-за введения итераторов только для перемещения (поскольку неконстант begin должен переместить итератор), но остается целью проекта.

Другими словами, subrange совсем не похоже pair , tuple , или array . Эти три являются совокупностями значений без семантики, и их get перегрузки отражают это, прозрачно распространяя квалификацию cv и категорию ценности. subrange с другой стороны, у него есть семантика — это не просто пара итератора и стража. Это get только для чтения, никогда для письма. Вот почему get при subrange возврате по значению.

Не имеет большого смысла предоставлять get перегрузку без значения const; о возврате по изменяемой ссылке не может быть и речи; возврат по ссылке на const не похож на все остальное (и также удивителен). Возврат по значению означает, что в единственном случае это имеет значение, get на lvalue subrange это разрушительная операция, которая была бы совершенно неожиданной.

Комментарии:

1. Другой вопрос, должны ли мы вводить дополнительные ограничения для первой перегрузки, чтобы устранить ошибку «отброшенный классификатор констант», возникающую внутри тела функции? Я планирую подать заявку на LWG для этого, но я не знаю, стоит ли это того.

2. Я не вижу причин запрещать get<1> больше, чем .end() . Это совершенно безвредно и может быть полезно. Однако я вижу некоторую ценность в проверке, которую begin можно вызвать в ограничении.