Span распространяет const?
Стандартные контейнеры распространяют const. То есть их элементы автоматически являются константными, если сами контейнеры являются константными. Например:
const std::vector vec{3, 1, 4, 1, 5, 9, 2, 6};
ranges::fill(vec, 314); // impossible
const std::list lst{2, 7, 1, 8, 2, 8, 1, 8};
ranges::fill(lst, 272); // impossible
Встроенные массивы также распространяют const:
const int arr[] {1, 4, 1, 4, 2, 1, 3, 5};
ranges::fill(arr, 141); // impossible
Тем не менее, я заметил, что std::span
(предположительно) не распространяется const. Минимальный воспроизводимый пример:
#include <algorithm>
#include <cassert>
#include <span>
namespace ranges = std::ranges;
int main()
{
int arr[] {1, 7, 3, 2, 0, 5, 0, 8};
const std::span spn{arr};
ranges::fill(spn, 173); // this compiles
assert(ranges::count(arr, 173) == 8); // passes
}
Почему этот код работает нормально? Почему std::span
обращаться с const иначе, чем со стандартными контейнерами?
2 ответа
Распространение const для типа как span
на самом деле не имеет особого смысла, так как не может защитить вас от чего бы то ни было.
Рассмотреть возможность:
void foo(std::span<int> const& s) {
// let's say we want this to be ill-formed
// that is, s[0] gives a int const& which
// wouldn't be assignable
s[0] = 42;
// now, consider what this does
std::span<int> t = s;
// and this
t[0] = 42;
}
Даже если s[0]
дал int const&
, t[0]
безусловно, дает int&
, А также t
относится к точно таким же элементам, как s
, В конце концов, это копия, и span
не владеет своими элементами - это ссылочный тип. Даже если s[0] = 42
не удалось, std::span(s)[0] = 42
будет успешным. Это ограничение никому не поможет.
Разница с обычными контейнерами (например, vector
является то, что копии здесь по-прежнему ссылаются на те же элементы, в то время как копирование vector
даст вам совершенно новые элементы.
Способ иметь span
относятся к неизменным элементам, чтобы не сделать span
сам const
это сделать основные элементы сами const
, Это: span<T const>
не span<T> const
,
Подумайте об указателях. Указатели также не распространяют const. Константность указателя не зависит от констант типа элемента.
Рассмотрен модифицированный минимальный воспроизводимый пример:
#include <algorithm>
#include <cassert>
#include <span>
namespace ranges = std::ranges;
int main()
{
int var = 42;
int* const ptr{&var};
ranges::fill_n(ptr, 1, 84); // this also compiles
assert(var == 84); // passes
}
По замыслу std::span
является своего рода указателем на непрерывную последовательность элементов. Per [span.iterators]:
constexpr iterator begin() const noexcept; constexpr iterator end() const noexcept;
Обратите внимание, что begin()
а также end()
вернуть неконстантный итератор независимо от того, является ли сам диапазон постоянным или нет. Таким образом, std::span
не распространяется const, способом, аналогичным указателям. Константность диапазона не зависит от констант типа элемента.
const1 std:: span< const2 ElementType, экстент>
Первый const
определяет константу самого диапазона. Секунда const
определяет постоянство элементов. Другими словами:
std::span< T> // non-const span of non-const elements
std::span<const T> // non-const span of const elements
const std::span< T> // const span of non-const elements
const std::span<const T> // const span of const elements
Если мы изменим декларацию spn
в Примере, чтобы:
std::span<const int, 8> spn{arr};
Код не компилируется, как и стандартные контейнеры. Неважно, отмечаете ли вы spn
Сам как конст в этом отношении. (Вы не можете делать такие вещи, как spn = another_arr
Впрочем, если пометить его как const)
(Примечание: вы все еще можете использовать вывод аргументов шаблона класса с помощью std::as_const
:
std::span spn{std::as_const(arr)};
Только не забудь #include <utility>
.)