简体   繁体   中英

Why can logical constness only be added to a std::span of const pointers?

Consider this code that attempts to create various std::span objects for a vector of raw pointers.

#include <vector>
#include <span>

int main()
{
    struct S {};
    std::vector<S*> v;
    std::span<S*> span1{v};
    std::span<S* const> span2{v};
    std::span<const S* const> span3{v};
    std::span<const S*> span4{v};
    return 0;
}

span3 compiles fine, but span4 fails with the following error:

<source>: In function 'int main()':
<source>:58:32: error: no matching function for call to 'std::span<const main()::S*>::span(<brace-enclosed initializer list>)'
   58 |     std::span<const S*> span4{v};
      |                                ^
In file included from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:45,
                 from <source>:5:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/span:231:9: note: candidate: 'template<class _OType, long unsigned int _OExtent>  requires (_Extent == std::dynamic_extent || _OExtent == std::dynamic_extent || _Extent == _OExtent) && (std::__is_array_convertible<_Type, _Tp>::value) constexpr std::span<_Type, _Extent>::span(const std::span<_OType, _OExtent>&) [with long unsigned int _OExtent = _OType; _Type = const main()::S*; long unsigned int _Extent = 18446744073709551615]'

I would either expected span3 and span4 both to fail or both to succeed. Can someone explain why logical constness can be added to to a std::span of raw pointers iff the underlying pointer is const (ie bitwise).

  1. std::span<const S*> allows you to assign a const S* to an element.
  2. std::vector<S*> allows you to read an element of type S* .
  3. If std::span<const S*> were allowed to take a std::vector<S*> , then it would be possible to sneakily convert a const S* to a S* , by assigning the const S* to an element of the span and then reading the same element through the vector.

That is to say, if std::span<const S*> span4{v}; were allowed, then the following program would be valid:

#include <vector>
#include <span>

int main()
{
    struct S { int value; };
    std::vector<S*> v = {nullptr};
    std::span<const S*> span4{v}; // Note: not allowed in reality

    const S s{.value = 0};

    span4[0] = &s;
    S* p = v[0];
    assert(p == &s);
    p->value = 42; // Oops, modifies the value of a const object!
}

So in order to prevent this scenario and provide const-correctness, std::span<const S*> must not be constructible from a std::vector<S*> .

On the other hand, std::span<const S* const> does not allow you to assign to its elements, so it's safe for it to take a std::vector<S*> .

(Yes, it's the same reason that you can convert a S** to const S* const * , but you cannot convert it to const S** .)

The range version of the span constructor has the following constraints :

is_convertible_v<U(*)[], element_type(*)[]> is true .

[ Note 5 : The intent is to allow only qualification conversions of the range reference type to element_type . end note ]

where U is remove_reference_t<ranges::range_reference_t<R>> , which in your case is S* , and element_type is the template type of span .

Then we can get the following

struct S {};

template<class U, class element_type>
concept array_convertible = std::is_convertible_v<U(*)[], element_type(*)[]>;

static_assert(array_convertible<S*, S*>);
static_assert(array_convertible<S*, S* const>);
static_assert(array_convertible<S*, const S* const>);
static_assert(array_convertible<S*, const S*>); // static assertion failed

Given that the constraints are not satisfied, using std::vector<S*> to initialize std::span<const S*> is ill-formed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM