C++23:多维视图(std::mdspan)
介绍
在 C++23 中,std::mdspan
是一个非拥有的多维视图,用于表示连续对象序列。这个连续对象序列可以是一个简单的 C 数组、带有大小的指针、std::array
、std::vector
或 std::string
。
这种多维视图通常被称为多维数组。
多维数组的形状由其维数(也称为秩)和每个维度的大小(也称为扩展)决定。std::mdspan
的大小是所有非零维度的大小的乘积。你可以使用多维索引运算符 []
来访问 std::mdspan
的元素。
每个 std::mdspan
的维度可以有静态或动态的扩展。静态扩展意味着其长度在编译时指定;动态扩展意味着其长度在运行时指定。
定义
template<
class T,
class Extents,
class LayoutPolicy = std::layout_right,
class AccessorPolicy = std::default_accessor<T>
> class mdspan;
- T: 连续对象序列的类型。
- Extents: 指定维数及其大小;每个维度可以有静态或动态的扩展。
- LayoutPolicy: 指定用于访问底层内存的布局策略。
- AccessorPolicy: 指定如何引用底层元素。
由于 C++17 中的类模板参数推导(CTAD),编译器通常可以自动从初始化器的类型推导出模板参数。
示例
使用动态扩展
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8}; // (1)
std::mdspan m{myVec.data(), 2, 4}; // (2)
std::cout << "m.rank(): " << m.rank() << '\n'; // (4)
for (std::size_t i = 0; i < m.extent(0); ++i) { // (6)
for (std::size_t j = 0; j < m.extent(1); ++j) { // (7)
std::cout << m[i, j] << ' '; // (8)
}
std::cout << '\n';
}
std::cout << '\n';
std::mdspan m2{myVec.data(), 4, 2}; // (3)
std::cout << "m2.rank(): " << m2.rank() << '\n'; // (5)
for (std::size_t i = 0; i < m2.extent(0); ++i) {
for (std::size_t j = 0; j < m2.extent(1); ++j) {
std::cout << m2[i, j] << ' ';
}
std::cout << '\n';
}
}
使用静态和动态扩展
#include <mdspan>
#include <iostream>
#include <string>
#include <vector>
#include <tuple>
int main() {
std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};
std::mdspan<int, std::extents<std::size_t, 2, 4>> m{myVec.data()}; // (1)
std::cout << "m.rank(): " << m.rank() << '\n';
for (std::size_t i = 0; i < m.extent(0); ++i) {
for (std::size_t j = 0; j < m.extent(1); ++j) {
std::cout << m[i, j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
std::mdspan<int, std::extents<std::size_t, std::dynamic_extent,
std::dynamic_extent>> m2{myVec.data(), 4, 2}; // (2)
std::cout << "m2.rank(): " << m2.rank() << '\n';
for (std::size_t i = 0; i < m2.extent(0); ++i) {
for (std::size_t j = 0; j < m2.extent(1); ++j) {
std::cout << m2[i, j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
}
布局策略
std::mdspan
允许您指定用于访问底层内存的布局策略。默认情况下,使用 std::layout_right
(C、C++ 或 Python 风格),但您也可以指定 std::layout_left
(Fortran 或 MATLAB 风格)。
使用布局策略std::mdspan
和std::layout_right
遍历两个std::layout_left
可以看出差异。
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};
std::mdspan<int, std::extents<std::size_t, // (1)
std::dynamic_extent, std::dynamic_extent>,
std::layout_right> m{myVec.data(), 4, 2};
std::cout << "m.rank(): " << m.rank() << '\n';
for (std::size_t i = 0; i < m.extent(0); ++i) {
for (std::size_t j = 0; j < m.extent(1); ++j) {
std::cout << m[i, j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
std::mdspan<int, std::extents<std::size_t, // (2)
std::dynamic_extent, std::dynamic_extent>,
std::layout_left> m2{myVec.data(), 4, 2};
std::cout << "m2.rank(): " << m2.rank() << '\n';
for (std::size_t i = 0; i < m2.extent(0); ++i) {
for (std::size_t j = 0; j < m2.extent(1); ++j) {
std::cout << m2[i, j] << ' ';
}
std::cout << '\n';
}
}
接口概览
md[ind]
: 访问第 ind 个元素。md.size
: 返回多维数组的大小。md.rank
: 返回多维数组的维数。md.extents(i)
: 返回第 i 个维度的大小。md.data_handle
: 返回指向连续元素序列的指针。
参考:MC++ BLOG – Der Blog für Modernes C++ von Rainer Grimm (modernescpp.com)