Как написать функцию C++20, принимающую диапазон как с T, так и с const T?

Я определил следующую функцию:

template<
    std::ranges::contiguous_range R,
    typename T = std::ranges::range_value_t<R>
>
std::span<T> foo(R&& r, const T& someValue) {
    std::span<T> sp{r.begin(), r.end()};
    /// ...
    return sp;
}

Теперь у меня есть три варианта использования:

std::vector<std::string> baseVec;
auto a = foo(baseVec, {""});

std::span<std::string> sp{baseVec};
auto b = foo(sp, {""});

const std::vector<std::string>& ref = baseVec;
auto c = foo(ref, {""}); // <------------------ problem here!

Насколько я понял, foo(ref) не удастся скомпилировать, потому что span, созданный внутри foo, имеет тип span<T>, тогда как в данном случае он должен быть span<const T>.

Итак, как мне написать foo, чтобы он принимал все три случая?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
119
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Можно пойти по пресловутому пути. Если компилятор такой умный, пусть он сам определит тип. Это возможно благодаря вычету аргументов шаблона класса.

template<
    std::ranges::contiguous_range R,
    typename T = std::ranges::range_value_t<R>
>
auto foo(R&& r, const T&) {
    std::span sp (r.begin(), r.end()); // CTAD + auto return type
    /// ...
    return sp;
}
Ответ принят как подходящий

Проблема в том, что value_type диапазона никогда не является const-квалифицированным. Но тип диапазона reference может быть - хотя это все равно неправильно, потому что это будет string const&.

Непрерывные итераторы должны иметь тип ссылки U& для некоторого типа U, поэтому, если мы просто отбросим конечную ссылку, мы получим потенциально константный тип:

template<
    std::ranges::contiguous_range R,
    typename T = std::remove_reference_t<std::ranges::range_reference_t<R>>
>
std::span<T> foo(R&& r, const T& someValue) {
    std::span<T> sp(r);
    /// ...
    return sp;
}

Обратите внимание, что вам не нужно передавать r.begin() и r.end() в span, span имеет конструктор диапазона.

Однако это все же не совсем верно по той причине, что оригинал тоже был неверным. contiguous здесь недостаточный критерий, нам также нужно, чтобы диапазон был sized, чтобы мы могли построить из него span:

template<
    std::ranges::contiguous_range R,
    typename T = std::remove_reference_t<std::ranges::range_reference_t<R>>
>
    requires std::ranges::sized_range<R>
std::span<T> foo(R&& r, const T& someValue) {
    std::span<T> sp(r);
    /// ...
    return sp;
}

Кроме того, вы, вероятно, не хотите делать вывод T из аргумента — вы действительно хотите, чтобы это был именно правильный T, связанный с диапазоном:

template<
    std::ranges::contiguous_range R,
    typename T = std::remove_reference_t<std::ranges::range_reference_t<R>>
>
    requires std::ranges::sized_range<R>
std::span<T> foo(R&& r, std::type_identity_t<T> const& someValue) {
    std::span<T> sp(r);
    /// ...
    return sp;
}

Обратите внимание, что я изменил ваш код, чтобы построить span, используя круглые скобки вместо фигурных скобок. Это связано с тем, что передача пары итераторов в инициализатор с использованием фигурных скобок не является хорошей идеей, поскольку легко может привести к неправильным результатам. Учитывать:

auto x = std::vector{1, 2, 3, 4};

auto a = std::vector(x.begin(), x.end()); // four ints: [1, 2, 3, 4]
auto b = std::vector{x.begin(), x.end()}; // two iterators: [x.begin(), x.end()]

То же самое верно и для span, у которого будет конструктор initializer_list, начиная с C++26.

Какой-то странный последний раздел, поскольку окончательная версия вообще не передает даже пару итераторов. Предположительно, диапазон c'tor и initializer_list c'tor будут должным образом ограничены, чтобы не наступать друг другу на пятки, поэтому диапазон можно будет передавать с помощью фигурных или круглых скобок.

StoryTeller - Unslander Monica 02.08.2024 22:26

@StoryTeller-UnslanderMonica Я не думаю, что когда-либо странно указывать на плохие практики.

Barry 02.08.2024 23:21

Я не согласен, но в начальном разделе есть хорошая тематическая последовательность, очень ясная и информативная. Тогда как второй просто не очень хорошо с ним сочетается, даже в качестве дополнения. Это больше похоже на случайную запоздалую мысль. Редактирование, направленное на их большее связывание, сделало бы его менее заметным (и улучшило бы общий поток ответа).

StoryTeller - Unslander Monica 02.08.2024 23:25

Хм, а как работает часть std::type_identity_t, есть какие-нибудь практические примеры?

康桓瑋 03.08.2024 06:03

ОТ, но не могу себя сдержать. Заставить span построить из initializer_list — неловкий шаг. Я надеялся увидеть initializer_list устаревшим. span<const T> может быть просто заменой.

Red.Wave 03.08.2024 17:16

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