13

标准容器传播 const。也就是说,如果容器本身是 const,则它们的元素自动为 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 不同于标准容器?

4

2 回答 2

17

为 like 类型传播 constspan实际上并没有多大意义,因为它无论如何都无法保护您免受任何伤害。

考虑:

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)的不同之处在于,这里的副本仍然引用相同的元素,而复制 avector会为您提供全新的元素。

引用不可变元素的方法span不是制作span自身const,而是制作底层元素本身const。即:span<T const>,不是span<T> const

于 2019-07-05T02:22:43.627 回答
4

想想指针。指针也不传播 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()跨度本身是否为 const,都返回一个非常量迭代器。因此,std::span不会以类似于指针的方式传播 const。跨度的常数与元素类型的常数无关。

const 1 std::span< const 2 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>。)

于 2019-07-04T10:56:26.243 回答