Я определил следующую функцию:
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_list
c'tor будут должным образом ограничены, чтобы не наступать друг другу на пятки, поэтому диапазон можно будет передавать с помощью фигурных или круглых скобок.