The purpose is to allow implicitly converting between spans, as long as this conversion is a qualifying conversion and the sizes match.
It can also be used for explicit conversions like in your exaples, but (8) is special because it's explicit
only in a few cases. For example:
void process_all(std::span<const int> numbers);
std::span<int> get_range();
void foo() {
auto r = get_range(); // ok, sizes match and qualifying conversion takes place
process_all(r);
}
This is possible, because there is a qualifying conversion from int
to const int
, so we can convert std::span<int>
to std::span<const int>
.
This use case is quite common, because making const std::span<T>
doesn't ensure immutability of the elements, only a std::span<const T>
does that.
This qualifying conversion can actually be multiple levels deep, e.g. we can:
- take a
std::span<const int * const * const>
as a function parameter
- pass a
std::span<int**>
to it
However, that's not a unique property of this constructor, (7) does the same.
You may ask: "Why don't we just use constructor (7)?"
template< class R >
explicit(extent != std::dynamic_extent)
constexpr span( R&& range ); // (7)
This constructor works with any sized contiguous range, and a span is one.
However, it is always explicit for a static extent, and only constructor (8) allows an implicit conversion between equally sized static spans. For example:
#include <span>
void process_all(std::span<const int, 100> numbers);
std::span<int, 100> get_range();
std::span<int, 10> get_short_range();
std::span<int> get_dynamic_range();
void foo() {
process_all(get_range()); // OK
process_all(get_short_range()); // ill-formed, need explicit conversion
process_all(get_dynamic_range()); // ill-formed, need explicit conversion
}
The examples that you've given all use explicit conversions, so they don't demonstrate what makes (8) special, namely that it's conditionally explicit in fewer situations than other constructors.