Span распространяет const? - PullRequest
7 голосов
/ 04 июля 2019

Стандартные контейнеры распространяют 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 ]

5 голосов
/ 05 июля 2019

Распространение 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.

5 голосов
/ 04 июля 2019

Подумайте об указателях.Указатели также не распространяют 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 является своего рода указателемдо смежной последовательности элементов.За [span.iterators] :

constexpr iterator begin() const noexcept;
constexpr iterator end() const noexcept;

Обратите внимание, что begin() и end() возвращают неконстантный итератор независимо от того, является ли сам диапазонпостоянный или нет.Таким образом, std::span не распространяет const способом, аналогичным указателям.Константа диапазона не зависит от констант типа элемента.

<i>const</i><sub>1</sub> std::span<<i>const</i><sub>2</sub> ElementType, Extent>

Первый 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 как const.(Вы не можете делать такие вещи, как spn = another_arr, хотя, если вы пометите его как const)

(Примечание: вы все равно можете использовать вывод аргументов шаблона класса с помощью std::as_const:

std::span spn{std::as_const(arr)};

Только не забудьте #include <utility>.)

...