为什么std::span是一个指针+大小而不是两个迭代器?

10

看起来 C++20 中的 std::span 定义类似于

template<class T>
class span
     {
     T* begin;
     size_t count;
     };

而不是

template<class Iter>
class span
     {
     Iter begin;
     Iter end;
     };

哪个更通用(适用于std::list, std::map等)?

1个回答

13
std::span<T>的整个意义在于它是对于连续数据的一种视图。使用pair<T*, size_>(或类似的类型)来表示这种视图是正确的方式。你不能有一个std::span是针对std::liststd::map的视图,因此想出一种表示方法是没有意义的。重点是要成为一种通用的词汇类型,只接受连续的数据。
同样重要的是span实际上是类型擦除的。一个span<int>可以引用到一个int[20]、一个vector<int>或者一个动态分配的int[],或者一个llvm::SmallVector<int>等等。它的来源并不重要,你只需要这一个类型:"查看一些连续的int "。
确实,pair<Iter, Iter>(或更一般的情况下,pair<Iter, Sentinel>)是一种更通用的表示方法,适用于更多的容器。在C++20中也有这样一种东西,它被称为std::ranges::subrange<I, S>。但是请注意,这里没有类型擦除的方面...一个针对map<K, V>subrange将具有不同的类型,而一个针对相同value_type的不同容器的subrange,如list<pair<K const, V>>vector<pair<K const, V>>multimap<K, V>

但是您可以在任何其他情况下使用子范围,而不是使用跨度。那么,不同类型的原因是,如果您为简单情况使用单独的类,则可以提供更优化的实现吗? - user877329
@user877329 这并不是关于“最优”的问题... subrange<T*> 在表示上基本上等同于 span<T>。但它们的可用性不同。vector<T> 明确可以转换为 span<T>,但不一定可以转换为 subrange<T*>。此外,模板参数的意义也不同,即 TT*span 只是更具体用例的更具体东西。 - Barry
2
@user877329: "但你可以在任何你原本使用span的地方使用subrange。"不,你不能这样做。 你可以将span的数据传递给C API;而对于任意迭代器子范围,你不能这样做。 - Nicol Bolas
如果subrange构造函数接受两个迭代器,那么@Barry的代码subrange<T*> range{vec.data(), vec.data() + size()};就是正确的。 - user877329
@NicolBolas 如果Iter是一个指针,我应该能够这样做。即使我可以将其传递给C API,也必须存在这样的API,大多数C API使用指针和大小,而不是跨度结构,因此C兼容性并不是一个很大的问题,除非您重写POSIX或Windows API以及一堆其他C库,但您没有这样做。当然,可能会发生调用foo(T*,size_t)foo(std :: span <T>)在汇编中看起来完全相同,但强制不同的函数签名绝对是未定义行为。 - user877329
显示剩余3条评论

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