Я определил следующую функцию:
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, чтобы он принимал все три случая?





Можно пойти по пресловутому пути. Если компилятор такой умный, пусть он сам определит тип. Это возможно благодаря вычету аргументов шаблона класса.
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.
@StoryTeller-UnslanderMonica Я не думаю, что когда-либо странно указывать на плохие практики.
Я не согласен, но в начальном разделе есть хорошая тематическая последовательность, очень ясная и информативная. Тогда как второй просто не очень хорошо с ним сочетается, даже в качестве дополнения. Это больше похоже на случайную запоздалую мысль. Редактирование, направленное на их большее связывание, сделало бы его менее заметным (и улучшило бы общий поток ответа).
Хм, а как работает часть std::type_identity_t, есть какие-нибудь практические примеры?
ОТ, но не могу себя сдержать. Заставить span построить из initializer_list — неловкий шаг. Я надеялся увидеть initializer_list устаревшим. span<const T> может быть просто заменой.
Какой-то странный последний раздел, поскольку окончательная версия вообще не передает даже пару итераторов. Предположительно, диапазон c'tor и
initializer_listc'tor будут должным образом ограничены, чтобы не наступать друг другу на пятки, поэтому диапазон можно будет передавать с помощью фигурных или круглых скобок.