Does span propagate const?
Think of pointers. Pointers do not propagate const either. The constness of the pointer is independent from the constness of the element type.
Considered the modified Minimal Reproducible Example:
#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
}
It is by design that std::span
is kind of a pointer to a contiguous sequence of elements. Per [span.iterators]:
constexpr iterator begin() const noexcept; constexpr iterator end() const noexcept;
Note that begin()
and end()
return a non-const iterator regardless of whether the span itself is const or not. Thus, std::span
does not propagate const, in a way that is analogous to pointers. The constness of the span is independent from the constness of the element type.
const1 std::span<const2 ElementType, Extent>
The first const
specifies the constness of the span itself. The second const
specifies the constness of the elements. In other words:
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
If we change the declaration of spn
in the Example to:
std::span<const int, 8> spn{arr};
The code fails to compile, just like the standard containers. It doesn't matter whether you mark spn
itself as const in this regard. (You can't do things like spn = another_arr
, though, if you mark it as const)
(Note: you can still use class template argument deduction with the help of std::as_const
:
std::span spn{std::as_const(arr)};
Just don't forget to #include <utility>
.)
Propagating const for a type like span
doesn't actually make much sense, since it cannot protect you from anything anyway.
Consider:
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;
}
Even if s[0]
gave an int const&
, t[0]
surely gives an int&
. And t
refers to the exactly same elements as s
. It's a copy after all, and span
doesn't own its elements - it's a reference type. Even if s[0] = 42
failed, std::span(s)[0] = 42
would succeed. This restriction wouldn't do anyone any good.
The difference with the regular containers (e.g. vector
) is that the copies here still refer to the same elements, whereas copying a vector
would give you entirely new elements.
The way to have span
refer to immutable elements isn't to make the span
itself const
, it's to make the underlying elements themselves const
. That is: span<T const>
, not span<T> const
.