【问题标题】:Does span propagate const?span 是否传播 const?
【发布时间】:2019-11-15 02:34:12
【问题描述】:

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

【问题讨论】:

    标签: c++ constants c++20 std-span


    【解决方案1】:

    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&amp;t[0] 肯定给出了int&amp;。而t 指的是与s 完全相同的元素。毕竟它是一个副本,span 不拥有它的元素——它是一个引用类型。即使s[0] = 42 失败,std::span(s)[0] = 42 也会成功。这个限制对任何人都没有好处。

    与常规容器(例如vector)的不同之处在于,此处的副本仍然引用相同的元素,而复制vector 将为您提供全新的元素。

    span 引用不可变元素的方法不是让span 本身成为const,而是让底层元素本身成为const。即:span&lt;T const&gt;,而不是span&lt;T&gt; const

    【讨论】:

    • 好点,解释了为什么 span 不能合理地传播 const。另外,s 是否打算作为参考?
    • @L.F.不, ref 并不重要,只是 const& 参数是一种非常典型的以 const span 结尾的方式。
    • 我接受了这个答案,因为它比我的解释得更好。
    【解决方案2】:

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

    const1 std::spanconst2 ElementType, Extent>

    第一个const 指定span 本身的常量。第二个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 &lt;utility&gt;。)

    【讨论】:

    • 请注意,我无法访问可以实际编译代码的编译器(目前),所以我需要更正。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-07
    • 2017-01-12
    • 1970-01-01
    • 2010-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多