跨度是否传播const?

21

标准容器传递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 的方式与标准容器不同?

2个回答

23

对于像span这样的类型,传播const实际上并没有太多意义,因为它无论如何都不能保护您免受任何影响。

考虑以下内容:

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)的区别在于,这里的拷贝仍然引用同样的元素,而拷贝 vector 将会给你全新的元素。
span 引用不可变的元素的方法并不是将 span 本身变为 const,而是使底层元素本身成为 const。也就是说:span<T const>,而不是 span<T> const

好的观点,解释了为什么span无法合理地传播const。此外,s是引用吗? - L. F.
@L.F. 不,引用并不重要,只是const&参数通常是最终得到const span的一种典型方式。 - Barry
我接受了这个答案,因为它的解释比我的好。 - L. F.
为什么在s是一个const span的情况下允许std::span<int> t = s;这样的操作呢?编译器在使用指针时是不允许这样的(正如其他答案告诉我们要将span与指针进行比较)。 - undefined
哦,对不起,我搞混了,没错!谢谢。 - undefined
显示剩余3条评论

8

想想指针。指针也不会传播const。指针的const与元素类型的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;
请注意,无论span本身是否为const,begin()end()都会返回非const迭代器。因此,std::span不会传播const,类似于指针。 span的const性质与元素类型的const性质是独立的。
const1 std::span<const2 ElementType, Extent>
第一个const指定span本身的const性质。第二个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并不重要。(但如果将其标记为const,则不能执行spn = another_arr之类的操作)

(注意:您仍然可以使用std::as_const来帮助进行类模板参数推导:

std::span spn{std::as_const(arr)};

只是别忘了要 #include <utility>


请注意,我目前没有访问可以编译代码的编译器,因此可能需要更正。 - L. F.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接