Take a look at this example:
#include <span>
#include <vector>
class Data
{
public:
Data() = default;
template<class R>
explicit Data(R& r)
: buf_(r)
, header_(buf_.first<4>())
{}
private:
std::span<char> buf_;
// compile error
// std::span<char, 4> header_;
// compiles but ill-formed (against the precondition of [span.sub])
std::span<char, 4> header_{buf_.first<4>()};
};
int main()
{
std::vector<char> buf(1234);
Data data{buf};
}
The reason of compile error is because it is explicitly disallowed in the standard [span.cons]:
constexpr span() noexcept;Constraints:
Extent == dynamic_extent || Extent == 0is true.
Postconditions:size() == 0 && data() == nullptr.
Why does this constraint exist?
Since default construction for a span with dynamic_extent is already allowed, it feels like these two cases are semantically same:
std::span<char> header_; // allowed
std::span<char, 4> header_; // why disallowed?
Furthermore, take a look at the wording of [span.obs]:
constexpr size_type size() const noexcept;Effects: Equivalent to:
return size_;
Let's say for example, the standard could even define stricter implementation details to encourage static optimization. If this wording was something like Returns Extent when Extent != dynamic_extent, the size_ data member can be omitted in runtime. If so, I understand that the default construction is invalid, because in that case size() will always return invalid size, i.e. Extent, which is not zero.
However, current standard exposes the non-static member variables in the class definition [span.overview]:
private:
pointer data_; // exposition only
size_type size_; // exposition only
Since we already have those variables, can't the standard library just set data_ = nullptr and size_ = 0 when statically sized span is default constructed? I surely can live with the current wording, but isn't the current standard expecting too strong constraints?
Note that the committee have once attempted to fix [span.cons] already in LWG3198, so I'm pretty sure that they have some rationale for current wording.
A default-constructed std::span<T> successfully refers to 0 objects of type T starting at nullptr. To what 3 objects of type T does std::span<T,3>() refer?
The size_ member always being present is purely a narrative device to simplify the specification; it means nothing and really isn’t there in practice for static-extent spans.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With