目录
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()}; } };