Как получить тип значений в диапазоне С++ 20 std::ranges?

Учитывая std::ranges::range в С++ 20, как я могу определить тип значений в этом диапазоне?

Я хочу написать функцию, которая делает std::vector из произвольного диапазона. Я бы хотел, чтобы эта функция имела красивое явное объявление. Что-то вроде:

template<std::ranges::range Range>
std::vector<std::value_type_t<Range>> make_vector(Range const&);

Следующее, кажется, работает, но объявление не является явным, а реализация уродлива (даже игнорируя тот факт, что он не выделяет правильный размер заранее, где это возможно).

  template<std::ranges::range Range>
  auto make_vector(Range const& range)
  {
    using IteratorType = decltype(std::ranges::begin(std::declval<Range&>()));
    using DerefType    = decltype(*std::declval<IteratorType>());
    using T            = std::remove_cvref_t<DerefType>;
    std::vector<T> retval;
    for (auto const& x: range) {
      retval.push_back(x);
    }
    return retval;
  }

Есть ли канонический/лучший/более короткий/красивый способ сделать это?

@ user17732522 это должен быть ответ, а не комментарий.

Marcus Müller 19.11.2022 01:48

Кстати, есть также iter_value_t, чтобы перейти от типов итераторов к типам значений.

Ranoiaetep 19.11.2022 01:53
Как настроить Tailwind CSS с React.js и Next.js?
Как настроить Tailwind CSS с React.js и Next.js?
Tailwind CSS - единственный фреймворк, который, как я убедился, масштабируется в больших командах. Он легко настраивается, адаптируется к любому...
LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Переключение светлых/темных тем
Переключение светлых/темных тем
В Microsoft Training - Guided Project - Build a simple website with web pages, CSS files and JavaScript files, мы объясняем, как CSS можно...
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel могут быть немного сложными, но с помощью Eloquent ORM и его моделей мы можем сделать это с легкостью. В этой...
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Карта дорог Беладжар PHP Laravel
Карта дорог Беладжар PHP Laravel
Laravel - это PHP-фреймворк, разработанный для облегчения разработки веб-приложений. Laravel предоставляет различные функции, упрощающие разработку...
2
2
138
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Признак типа, который вы ищете, пишется как std::ranges::range_value_t, а не std::value_type_t.

Кроме того, вся функция, которую вы пытаетесь написать здесь, является просто более ограниченной версией std::ranges::to, появившейся в C++23.

Ответ принят как подходящий

Давайте пройдемся по порядку:

template<std::ranges::range Range>
auto make_vector(Range const& range)

Это проверяет, является ли Range диапазоном, но range не является Range, это const Range. Возможно, что R — это range, а R const — нет, поэтому на самом деле вы не ограничиваете эту функцию должным образом.

Правильным ограничением будет:

template<typename Range>
    requires std::ranges::range<Range const>
auto make_vector(Range const& range)

Но тогда это ограничивает вас только константными диапазонами (что является ненужным ограничением), а затем требует, чтобы вы очень осторожно использовали Range const по всему телу (о чем очень легко забыть).

И то, и другое является причиной того, что с диапазонами вы захотите использовать ссылки для пересылки:

template<std::ranges::range Range>
auto make_vector(Range&& range)

Это будет правильно ограничивать вашу функцию.


Следующий:

using IteratorType = decltype(std::ranges::begin(std::declval<Range&>()));
using DerefType    = decltype(*std::declval<IteratorType>());
using T            = std::remove_cvref_t<DerefType>;

Для этих вещей есть признаки типа:

using IteratorType = std::ranges::iterator_t<Range>;
using DerefType = std::iter_reference_t<IteratorType>;

Или:

using DerefType = std::ranges::range_reference_t<Range>;

Но также, поскольку вам нужен тип значения, это (как уже указывалось):

using T = std::ranges::range_value_t<Range>;

Обратите внимание, что тип значения не обязательно является просто ссылочным типом с удаленными квалификаторами.


Наконец:

std::vector<T> retval;
for (auto const& x: range) {
    retval.push_back(x);
}
return retval;


Кроме того, вы захотите, по крайней мере:

if constexpr (std::ranges::sized_range<Range>) {
    retval.reserve(std::ranges::size(range));
}

Поскольку, если у нас есть доступный размер, было бы неплохо сократить выделение до одного.


Наконец, в C++23 make_vector(r) можно просто написать std::ranges::to<std::vector>(r). Это обеспечивает оптимизацию распределения, о которой я упоминал, но может быть дополнительно более производительным, поскольку внутренняя конструкция vector позволяет избежать постоянной проверки необходимости дополнительного распределения (что и должен делать push_back).

«Это проверка того, является ли Range range, но range не Range, а const Range». Ну, да. Вы хотите, чтобы компилятор мог остановить вас, если вы делаете с ним не-const вещи. Все так и должно быть.

Nicol Bolas 21.11.2022 16:10

@NicolBolas OP имеет неправильное ограничение, они проверяют, является ли R range, но используют R const как range - range<R> не подразумевает range<R const>. Существуют типы, для которых верно первое, но не второе (что означало бы, что ограничение проходит, но создание экземпляра все равно не удается). ОП не может делать с ним не-const вещи (если не считать const_cast, я думаю).

Barry 21.11.2022 16:14

Nicol Bolas 21.11.2022 16:18

Barry 21.11.2022 16:20

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