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>.)

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