span 会传播 const 吗?

Does span propagate const?

提问人:L. F. 提问时间:7/4/2019 最后编辑:L. F. 更新时间:7/5/2019 访问量:7959

问:

标准容器传播 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

#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
}

为什么这段代码工作正常?为什么对待 const 与标准容器不同?std::span

C++ 常量 C++20 std-span

评论


答:

8赞 L. F. 7/4/2019 #1

想想指针。指针也不传播 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
}

从设计上讲,它是一种指向连续元素序列的指针。根据 [span.iterators]std::span

constexpr iterator begin() const noexcept;
constexpr iterator end() const noexcept;

请注意,无论 span 本身是否为 const ,都返回一个非常量迭代器。因此,不会以类似于指针的方式传播 const。跨度的恒定性与元素类型的恒定性无关。begin()end()std::span

const1 std::span<const2 ElementType, Extent>

第一个指定跨度本身的恒定度。第二个指定元素的恒定性。换言之:constconst

      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};

代码无法编译,就像标准容器一样。在这方面,您是否将自己标记为常量并不重要。(但是,如果您将其标记为 const,则不能执行此类操作)spnspn = another_arr

(注意:您仍然可以借助以下方法使用类模板参数推导:std::as_const

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

只是别忘了。#include <utility>

评论

0赞 L. F. 7/4/2019
请注意,我还没有访问可以实际编译代码的编译器,所以我需要纠正。
24赞 Barry 7/5/2019 #2

为这样的类型传播 const 实际上没有多大意义,因为它无论如何都不能保护你免受任何事情的影响。span

考虑:

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&tsspans[0] = 42std::span(s)[0] = 42

与常规容器(例如)的区别在于,这里的副本仍然引用相同的元素,而复制 a 会给你全新的元素。vectorvector

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

评论

0赞 L. F. 7/5/2019
好点子,解释了为什么 span 不能合理地传播 const。另外,它是否打算作为参考?s
0赞 Barry 7/5/2019
@L.F. 不,ref 并不重要,只是 const& 参数是最终获得 const span 的一种非常典型的方法。
0赞 L. F. 8/6/2019
我接受了这个答案,因为它比我的解释得更好。
1赞 Gabriel Galli 10/14/2023
如果首先是常量跨度,为什么要允许工作?编译器在使用指针时不允许这样做(因为另一个答案告诉我们将 span 与指针进行比较)。std::span<int> t = s;s
0赞 Barry 10/15/2023
@GabrielGalli吧?您绝对可以复制指针。