将范围与动态范围结合使用
作为具有动态范围的span的第一个示例,让我们仅使用具有动态范围的span来转换上一篇文章的的示例
上篇文章链接:固定范围和动态范围 - 掘金 (juejin.cn)
#include <iostream>
#include <string>
#include <vector>
#include <ranges>
#include <algorithm>
#include <span>
#include <cassert>
template<typename T, std::size_t Sz>
void printSpan(std::span<T, Sz> sp)
{
for (const auto& elem: sp){
std::cout <<"<<elem<<"\"";
}
std::cout <<'\n';
}
int main() {
std::vector<std::string> vec{"New York", "Tokyo", "Rio", "Berlin", "Sydney"};
assert(vec.size()>=3);
std::span<const std::string> sp{vec.begin(),3};
std::cout << "first 3: ";
printSpan(sp);
std::ranges::sort(vec);
std::cout << "first 3 after sort():";
printSpan(sp);
auto oldCapa = vec.capacity();
vec.push_back("Cairo");
if (oldCapa != vec.capacity()) {
sp= std::span{vec.begin(),3};
}
std::cout << "first 3: "; printSpan(sp);
sp = std::span{vec.end() -3,vec.end()};
std::cout <<"last 3: ";
printSpan(sp);
std::cout <<"vec:";
printSpan(std::span{vec});
std::cout <<"sp:"; sp=vec;
printSpan(sp);
}
程序的输出如下:
first 3: "New York" "Tokyo" "Rio"
first 3 after sort(): "Berlin" "New York" "Rio" first 3: "Berlin" "New York" "Rio"
last 3: "Sydney" "Tokyo" "Cairo"
vec: "Berlin" "New York" "Rio" "Sydney" "Tokyo" "Cairo"
sp: "Berlin" "New York" "Rio" "Sydney" "Tokyo" "Cairo"
同样,让我们逐步浏览该示例。
声明一个Span
在main()中,我们首先初始化一个span。但是,这次我们没有指定一个固定的范围
std::vector<std::string> vec{"New York", "Rio", "Tokyo", "Berlin", "Sydney"};
assert(vec.size()>=3);
std::span<const std::string> sp3{vec.begin(),3};
我们甚至可以在这里使用CTAD,这样张成的空间就可以推断出元素的类型
std::span sp3{vec.begin(), 3}; //deduces std::span<std::string>
结果是元素必须是字符串,但我们可以用不同数量的元素分配序列。
对于固定范围的跨度:
-
程序员负责确保传递的元素序列是有效的。
-
永远不要用(返回的)临时值初始化一个span。
-
通过指定元素是 const std::string,我们不能通过跨度修改它们。
- std::span<const std::string> sp3{vec.begin(), 3}; //元素不能被修改
- const std::span<std::string> sp3{vec.begin(), 3}; //元素可以被修改
-
只有通过添加限定符(如const),才能为span使用与引用元素不同的元素类型。
-
你也可以使用data()来代替begin()。
传递和打印Span
接下来,我们打印span,并将其传递给一个通用的打印函数
printSpan(sp3);
您可能会惊讶于函数模板printSpan<>()仍然可以被调用,尽管它有一个用于span大小的模板参数
template<typename T, std::size_t Sz>
void printSpan(std::span<T, Sz> sp)
{
...
}
它可以工作,因为std:: span是具有伪大小std::dynamic范围的span的快捷方式
std::span<int> sp; //相当于std::span<int, std::dynamic extent
事实上,类模板std::span<>
的声明如下
namespace std {
template<typename ElementType,size_t Extent = dynamic_extent>
class span{
...
};
}
这样,就可以提供printSpan<>()
这样的函数模板,它既适用于固定的跨度,也适用于动态的跨度。当使用具有动态范围的span调用printSpan<>()
时,std::dynamic extent
作为size传递
std::span<int> sp;
printSpan(sp);
再次注意,span是按值传递的,并且再次注意:在函数内部,我们仍然可以修改元素,只要它们没有声明为const。
将容器分配给Span
程序的其余部分与版本中具有固定范围的跨度一样。
然而,我们在最后增加了一件事:我们将向量作为一个整体分配给张成的空间,并将它打印出来
std::span<const std::string> sp{vec.begin(),3};
printSpan(sp);
sp=vec;
printSpan(sp);
在这里,您可以看到对span的赋值可以改变元素的数量。
使用具有固定范围和动态范围的span
固定范围和动态范围都有各自的好处。
指定固定大小允许我们更好地检测违规(在运行时甚至在编译时)。
例如,不能将元素数量错误的std::array<>
分配给具有固定范围的span
std::vector vec{1, 2, 3};
std::array arr{1,2,3,4,5,6};
std::span<int, 3> sp3{vec};
std::span sp{vec};
sp3 =arr;
sp=arr;
使用具有动态区段的跨度提供了更大的灵活性
std::span<int> sp;
...
std::vector vec{1, 2, 3,4,5};
sp=vec;
sp = {vec.begin(), 3};
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情”