目录
std::span
std::span!
这个东西是标准库在C++20新的STL中加入进来的。他更加像是一个已有数组资源的视图。它可以动态指定长度,也可以静态指定长度。这个东西跟笔者在《C++ STL CookBook 4》中谈到的Ranges View编程范式是一致的,span是一个view.
#include <iostream>
#include <span>
#include <array>
void do_check_int_span(const std::span<int>& my_span) {
for (int i : my_span) {
std::cout << i << " ";
}
}
int main() {
std::array<int, 5> my_array{1, 2, 3, 4, 5};
std::span<int> my_span(my_array.begin(), my_array.end() - 2);
do_check_int_span(my_span);
std::cout << std::endl;
return 0;
}
它的存在是有趣的,因为我们现在可以拿到任何一个连续的容器的span视图了。就跟笔者在上一篇博客谈到的那样。
| Public Member Function | 返回值 | 描述 |
|---|---|---|
T& front() | 序列中的第一个元素 | 返回序列中的第一个元素 |
T& back() | 序列中的最后一个元素 | 返回序列中的最后一个元素 |
T& operator[] | 指定索引位置的元素 | 返回指定索引位置的元素 |
T* data() | 指向序列起始位置的指针 | 返回指向序列起始位置的指针 |
iterator begin() | 指向第一个元素的迭代器 | 返回指向第一个元素的迭代器 |
iterator end() | 指向最后一个元素后一个位置的迭代器 | 返回指向最后一个元素后一个位置的迭代器 |
iterator rbegin() | 指向第一个元素的反向迭代器 | 返回指向第一个元素的反向迭代器 |
iterator rend() | 指向最后一个元素后一个位置的反向迭代器 | 返回指向最后一个元素后一个位置的反向迭代器 |
size_t size() | 序列中的元素个数 | 返回序列中元素的数量 |
size_t size_bytes() | 序列所占字节数 | 返回序列所占字节数 |
bool empty() | 如果为空,返回 true | 如果序列为空,返回 true |
span<T> first(count) | 一个包含前 count 个元素的子 span | 返回一个包含前 count 个元素的子 span |
span<T> last(count) | 一个包含后 count 个元素的子 span | 返回一个包含后 count 个元素的子 span |
span<T> subspan(offset, count) | 一个从 offset 位置开始,包含 count 个元素的子 span | 返回一个从 offset 位置开始,包含 count 个元素的子 span |
结构化绑定
我知道很多人都写过这样的代码
MyErrorPackage handle_some_process(const ParamsPackage& package)
{
if( check_pramas_a( package ) )
{
return MyErrorPackage{ErrorCodeA, ErrorBufferStringA, AdviceA};
}
if( check_pramas_b( package ) )
{
return MyErrorPackage{ErrorCodeBErrorBufferStringB AdviceB)
}
...
handle_impl(package);
return MyErrorPackage{NO_ERROR, "No ERROR", ""};
}
// at caller
MyErrorPackage package = handle_some_process(package);
auto code = package.code;
auto errorString = package.errorString;
auto adviceString = package.advice;
...
不得不承认实在是有点抽象,写的太累了。一个方便的办法是使用我们的结构化绑定,什么意思呢?对于平凡的数据结构类型。比如说一个经典的C Structure,就是一个POD
struct MyErrorPackage
{
int code;
std::string buffer;
std::string advice;
}; // only contains data without method
很好,我们可以这样玩:
auto [code, buffer, advice] = handle_some_process(package);
一下我们就拿到了所有的成员。现在直接不用写那些取值代码吗,同时提升了性能。
在 if 和 switch 语句中初始化变量
从 C++17 开始,if 和 switch 语句现在具有初始化语法,类似于 C99 中的 for 循环。这使得你可以限制条件内使用的变量的作用域。也就是说,我们可以将那些没有必要泄漏到外面的,只是用在里面的if当中的变量放到我们的if - else结构当中:
const string artist{ "Jimi Hendrix" };
size_t pos{ artist.find("Jimi") };
if(pos != string::npos) {
cout << "found\n";
} else {
cout << "not found\n";
}
这种写法会将 pos 变量暴露到条件语句的外部,需要在其他地方管理,或者可能与其他使用同一标识符的代码发生冲突。现在,你可以将初始化表达式放在 if 语句条件中:
if(size_t pos{ artist.find("Jimi") }; pos != string::npos) {
cout << "found\n";
} else {
cout << "not found\n";
}
我们来看看,其实就是这个意思:
if(auto var{ init_value }; condition) {
// var 在这里可见
} else {
// var 仍然在这里可见
}
// var 在这里不可见
在这个例子中,初始化表达式定义的变量在整个 if 语句的范围内可见,包括 else 部分。一旦控制流退出 if 语句的作用域,变量将不再可见,并且相关的析构函数会被调用。
相同的,我们作用到switch语句里去,使用初始化表达式与 switch 语句:
switch(auto var{ init_value }; var) {
case 1: ...
case 2: ...
case 3: ...
...
default: ...
}
// var 在这里不可见
在 switch 语句中,初始化表达式定义的变量在整个 switch 语句的作用域内可见,包括所有的 case 部分和 default 部分。如果控制流离开 switch 语句的作用域,变量将不再可见,并且相关的析构函数会被调用。
一个有趣的应用场景是限制 lock_guard(用于锁定互斥量)的作用域。这可以通过初始化表达式轻松实现:
if (lock_guard<mutex> lg{ my_mutex }; condition) {
// 这里会发生有趣的事情
}
在构造函数中,lock_guard 锁定了互斥量,并且在析构函数中自动解锁。当 if 语句的作用域结束时,lock_guard 会自动销毁。在过去,你需要手动删除它或用额外的花括号将整个 if 语句包裹起来。
使用模板参数推导简化代码
模板参数推导发生在模板函数或类模板构造函数(从 C++17 开始)中,当参数的类型足够明确时,编译器可以自动推导出模板类型,而无需显式指定模板参数。这个特性有一些规则,但大多数情况下非常直观。
一般来说,模板参数推导在使用与模板参数兼容的参数时会自动发生。在函数模板中,参数推导通常像这样工作:
template<typename T>
const char * f(const T a) {
return typeid(T).name();
}
int main() {
cout << format("T is {}\n", f(47));
cout << format("T is {}\n", f(47L));
cout << format("T is {}\n", f(47.0));
cout << format("T is {}\n", f("47"));
cout << format("T is {}\n", f("47"s));
}
T is int T is long T is double T is char const * T is class std::basic_string<char...>
由于类型非常容易识别,因此无需在调用时显式指定模板参数,例如 f<int>(47)。编译器可以根据传入的参数推导出 <int> 类型。
对于多个模板参数,推导同样有效:
template<typename T1, typename T2>
string f(const T1 a, const T2 b) {
return format("{} {}", typeid(T1).name(), typeid(T2).name());
}
int main() {
cout << format("T1 T2: {}\n", f(47, 47L));
cout << format("T1 T2: {}\n", f(47L, 47.0));
cout << format("T1 T2: {}\n", f(47.0, "47"));
}
T1 T2: int long T1 T2: long double T1 T2: double char const *
在这个例子中,编译器推导出了 T1 和 T2 的类型。
-
注意,类型必须与模板兼容。例如,你不能对字面量类型取引用:
template<typename T>
const char * f(const T& a) {
return typeid(T).name();
}
int main() {
int x{47};
f(47); // 编译错误
f(x); // 这行编译通过
}
-
从 C++17 开始,模板参数推导也适用于类。因此,现在可以这样写:
pair p(47, 47.0); // 推导为 pair<int, double> tuple t(9, 17, 2.5); // 推导为 tuple<int, int, double>
这消除了使用 std::make_pair() 和 std::make_tuple() 的需求,因为你现在可以直接初始化这些类,而不需要显式指定模板参数。为了兼容旧代码,std::make_* 辅助函数仍然可用。
更加高级的折叠表达式
考虑一个包含参数包的构造函数:
cpp复制代码template <typename T>
class Sum {
T v{};
public:
template <typename... Ts>
Sum(Ts&& ... values) : v{ (values + ...) } {}
const T& value() const { return v; }
};
构造函数中使用了一个折叠表达式(values + ...),这是 C++17 的特性,应用操作符到参数包中的所有成员。在此示例中,它将 v 初始化为参数包的和。
该类的构造函数接受任意数量的参数,每个参数可以是不同的类。例如,我可以这样调用:
Sum s1 { 1u, 2.0, 3, 4.0f }; // unsigned, double, int, float
Sum s2 { "abc"s, "def" }; // std::string, c-string
然而,这不会编译,因为模板参数推导无法为这些不同类型的参数找到一个共同的类型。错误信息大概是:
cannot deduce template arguments for 'Sum'
我们可以通过模板推导指南来解决这个问题。推导指南是一个帮助模式,辅助编译器进行复杂的推导。以下是针对我们的构造函数的指南:
template <typename... Ts> Sum(Ts&& ... ts) -> Sum<std::common_type_t<Ts...>>;
这告诉编译器使用 std::common_type_t 特性,尝试为所有参数包中的类型找到一个共同的类型。现在,参数推导就会成功,并且我们可以查看它使用的类型:
Sum s1 { 1u, 2.0, 3, 4.0f }; // unsigned, double, int, float
Sum s2 { "abc"s, "def" }; // std::string, c-string
auto v1 = s1.value();
auto v2 = s2.value();
cout << format("s1 is {} {}, s2 is {} {}", typeid(v1).name(), v1, typeid(v2).name(), v2);
输出:
s1 is double 10, s2 is class std::string abcdef
扩展:MSVC下的Span实现
class span : private _Span_extent_type<_Ty, _Extent> {
private:
using _Base = _Span_extent_type<_Ty, _Extent>;
using _Base::_Mydata; // 这个是T*
using _Base::_Mysize; // 这个是size
public:
// 类型定义
using element_type = _Ty;
using value_type = remove_cv_t<_Ty>;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = _Ty*;
using const_pointer = const _Ty*;
using reference = _Ty&;
using const_reference = const _Ty&;
using iterator = _Span_iterator<_Ty>;
using reverse_iterator = _STD reverse_iterator<iterator>;
#if _HAS_CXX23
using const_iterator = _STD const_iterator<iterator>;
using const_reverse_iterator = _STD const_iterator<reverse_iterator>;
#endif
static constexpr size_type extent = _Extent;
// 构造函数
constexpr span() noexcept
requires (_Extent == 0 || _Extent == dynamic_extent) = default;
// 从迭代器构造
template <_Span_compatible_iterator<element_type> _It>
constexpr explicit(_Extent != dynamic_extent) span(_It first, size_type count) noexcept
: _Base(_STD to_address(_STD _Get_unwrapped_n(first, count)), count) {
#if _CONTAINER_DEBUG_LEVEL > 0
if constexpr (_Extent != dynamic_extent) {
_STL_VERIFY(count == _Extent, "无法构造具有静态范围的 span,范围大小不一致");
}
#endif
}
// 从范围构造
template <_Span_compatible_iterator<element_type> _It, _Span_compatible_sentinel<_It> _Sentinel>
constexpr explicit(_Extent != dynamic_extent) span(_It first, _Sentinel last) noexcept(noexcept(last - first))
: _Base(_STD to_address(first), static_cast<size_type>(last - first)) {
_STD _Adl_verify_range(first, last);
#if _CONTAINER_DEBUG_LEVEL > 0
if constexpr (_Extent != dynamic_extent) {
_STL_VERIFY(last - first == _Extent, "无法从范围构造具有静态范围的 span");
}
#endif
}
// 从数组构造
template <size_t _Size>
requires (_Extent == dynamic_extent || _Extent == _Size)
constexpr span(type_identity_t<element_type> (&arr)[_Size]) noexcept
: _Base(arr, _Size) {}
// 从数组构造(非 const)
template <class _OtherTy, size_t _Size>
requires (_Extent == dynamic_extent || _Extent == _Size) && is_convertible_v<_OtherTy (*)[], element_type (*)[]>
constexpr span(array<_OtherTy, _Size>& arr) noexcept
: _Base(arr.data(), _Size) {}
// 从数组构造(const)
template <class _OtherTy, size_t _Size>
requires (_Extent == dynamic_extent || _Extent == _Size) && is_convertible_v<const _OtherTy (*)[], element_type (*)[]>
constexpr span(const array<_OtherTy, _Size>& arr) noexcept
: _Base(arr.data(), _Size) {}
// 从范围构造
template <_Span_compatible_range<element_type> _Rng>
constexpr explicit(_Extent != dynamic_extent) span(_Rng&& range)
: _Base(_RANGES data(range), static_cast<size_type>(_RANGES size(range))) {
#if _CONTAINER_DEBUG_LEVEL > 0
if constexpr (_Extent != dynamic_extent) {
_STL_VERIFY(_RANGES size(range) == _Extent, "无法从范围构造具有静态范围的 span");
}
#endif
}
// 从另一个 span 构造
template <class _OtherTy, size_t _OtherExtent>
requires (_Extent == dynamic_extent || _OtherExtent == dynamic_extent || _Extent == _OtherExtent)
&& is_convertible_v<_OtherTy (*)[], element_type (*)[]>
constexpr explicit(_Extent != dynamic_extent && _OtherExtent == dynamic_extent)
span(const span<_OtherTy, _OtherExtent>& other) noexcept
: _Base(other.data(), other.size()) {
#if _CONTAINER_DEBUG_LEVEL > 0
if constexpr (_Extent != dynamic_extent) {
_STL_VERIFY(other.size() == _Extent, "无法从另一个 span 构造具有静态范围的 span");
}
#endif
}
// 获取子视图
template <size_t _Count>
_NODISCARD constexpr auto first() const noexcept {
if constexpr (_Extent != dynamic_extent) {
static_assert(_Count <= _Extent, "范围大小超出限制");
}
return span<element_type, _Count>{_Mydata, _Count};
}
_NODISCARD constexpr auto first(const size_type _Count) const noexcept {
return span<element_type, dynamic_extent>{_Mydata, _Count};
}
// 获取子视图(从后面开始)
template <size_t _Count>
_NODISCARD constexpr auto last() const noexcept {
if constexpr (_Extent != dynamic_extent) {
static_assert(_Count <= _Extent, "范围大小超出限制");
}
return span<element_type, _Count>{_Mydata + (_Mysize - _Count), _Count};
}
_NODISCARD constexpr auto last(const size_type _Count) const noexcept {
return span<element_type, dynamic_extent>{_Mydata + (_Mysize - _Count), _Count};
}
// 获取子视图(指定偏移量和大小)
template <size_t _Offset, size_t _Count = dynamic_extent>
_NODISCARD constexpr auto subspan() const noexcept {
using _ReturnType = span<element_type, _Count != dynamic_extent ? _Count : (_Extent != dynamic_extent ? _Extent - _Offset : dynamic_extent)>;
return _ReturnType{_Mydata + _Offset, _Count == dynamic_extent ? _Mysize - _Offset : _Count};
}
_NODISCARD constexpr auto subspan(const size_type _Offset, const size_type _Count = dynamic_extent) const noexcept {
return span<element_type, dynamic_extent>{_Mydata + _Offset, _Count == dynamic_extent ? _Mysize - _Offset : _Count};
}
// 获取 span 大小(元素个数)
_NODISCARD constexpr size_t size() const noexcept {
return _Mysize;
}
// 获取 span 大小(字节数)
_NODISCARD constexpr size_type size_bytes() const noexcept {
return _Mysize * sizeof(element_type);
}
// 判断是否为空
_NODISCARD constexpr bool empty() const noexcept {
return _Mysize == 0;
}
// 获取指定元素
_NODISCARD constexpr reference operator[](const size_type off) const noexcept {
return _Mydata[off];
}
// 获取第一个元素
_NODISCARD constexpr reference front() const noexcept {
return _Mydata[0];
}
// 获取最后一个元素
_NODISCARD constexpr reference back() const noexcept {
return _Mydata[_Mysize - 1];
}
// 获取指向数据的指针
_NODISCARD constexpr pointer data() const noexcept {
return _Mydata;
}
// 迭代器支持
_NODISCARD constexpr iterator begin() const noexcept {
return {_Mydata};
}
_NODISCARD constexpr iterator end() const noexcept {
return {_Mydata + _Mysize};
}
_NODISCARD constexpr reverse_iterator rbegin() const noexcept {
return reverse_iterator{end()};
}
_NODISCARD constexpr reverse_iterator rend() const noexcept {
return reverse_iterator{begin()};
}
};









