C++2b(C++23)標準ライブラリに追加される多次元ビューstd::mdspan
クラステンプレート(→id:yohhoy:20230303)の、コンストラクタと推論ガイド(deduction guide)による実引数クラステンプレート推論(CTAD; Class Template Argument Deduction)のあれこれ。
mdspan
型における次元情報std::extents<I, En...>
では、各次元の要素数(En
)はコンパイル時定数/実行時動的指定(std::dynamic_extent
)のいずれかとなる。ほとんどのCTAD*1では動的サイズ(std::dextents<I, R>
)に型推論される。
デフォルトコンストラクタ
動的要素数次元を含む場合のみデフォルト構築可能。
// 2次元ビュー/動的サイズ using Matrix = std::mdspan<double, std::dextents<size_t, 2>>; Matrix m1; // OK: 空(サイズ0x0)の2次元ビュー // 2次元ビュー/動的+固定サイズNx3 using ViewNx3 = std::mdspan<double, std::extents<size_t, std::dynamic_extent, 3>>; ViewNx3 m2; // OK: 空(サイズ0x3)の2次元ビュー // 2次元ビュー/固定サイズ3x3 using Mat3x3 = std::mdspan<double, std::extents<size_t, 3, 3>>; Mat3x3 m3; // NG: コンパイルエラー(ill-formed)
要素数リスト指定コンストラクタ
全次元の要素数リストまたは動的要素数の次元に対する要素数リストを、整数リスト/std::array<I, N>
/std::span<I, N>
にて指定する。CTAD利用時のインデクス型(mdspan::index_type
)は推論ガイドによりsize_t
となる。
// 整数リストによる要素数指定 double *ptr = /*...*/; // 2次元ビュー/動的サイズ2x3 using Matrix = std::mdspan<double, std::dextents<size_t, 2>>; Matrix m1{ptr, 2, 3}; // OK std::mdspan m2{ptr, 2, 3}; // OK: CTAD // 2次元ビュー/固定サイズ2x3 using Mat2x3 = std::mdspan<double, std::extents<size_t, 2, 3>>; Mat2x3 m3a{ptr}; // OK Mat2x3 m3c{ptr, 2}; // NG: ill-formed Mat2x3 m3b{ptr, 2, 3}; // OK Mat2x3 m3d{ptr, 0, 42}; // NG: 未定義動作(UB) // 固定サイズ次元におけるコンストラクタ引数の不一致は、 // std::extents(OtherIndexTypes...)コンストラクタの // 事前条件(Preconditions)違反のためUBを引き起こす。
// std::array<I, N>による要素数指定 double *ptr = /*...*/; std::array<int, 2> exts = {2, 3}; // 2次元ビュー/動的サイズ2x3 using Matrix = std::mdspan<double, std::dextents<size_t, 2>>; Matrix m1{ptr, exts}; // OK std::mdspan m2{ptr, exts}; // OK: CTAD
// std::span<I, N>による要素数指定 double *ptr = /*...*/; std::vector<int> exts_vec = {2, 3}; std::span<int, 2> exts{exts_vec}; // 2次元ビュー/動的サイズ2x3 using Matrix = std::mdspan<double, std::dextents<size_t, 2>>; Matrix m1{ptr, exts}; // OK std::mdspan m2{ptr, exts}; // OK: CTAD std::span<int, std::dynamic_extent> exts2{&exts_list[0], 2}; Matrix m3{ptr, exts2}; // NG: 動的サイズspanは指定不可 std::mdspan m4{ptr, exts2}; // NG: 動的サイズspanは指定不可
固定サイズ/動的サイズ変換
固定サイズから動的サイズへは常に安全に変換可能。動的サイズから固定サイズへはサイズ情報が一致していれば変換可能。
double *ptr = /*...*/; // 2次元ビュー型 using Matrix = std::mdspan<double, std::dextents<size_t, 2>>; using Mat3x3 = std::mdspan<double, std::extents<size_t, 3, 3>>; using Mat4x4 = std::mdspan<double, std::extents<size_t, 4, 4>>; // 固定サイズ→動的サイズ変換 Mat3x3 src3x3{ptr}; Matrix m1{src3x3}; // OK // 固定サイズ→固定サイズ変換 Mat4x4 m2{src3x3}; // NG: コンパイルエラー(ill-formed) // 動的サイズ→固定サイズ変換 Matrix srcmat{ptr, 3, 3}; Mat3x3 m3{srcmat}; // OK Mat4x4 m4{srcmat}; // NG: 未定義動作(UB) // 動的サイズ→固定サイズ型変換におけるサイズ不一致は // 事前条件(Preconditions)違反のためUBを引き起こす。
C配列型からの変換(CTAD)
1次元C配列型のみ直接変換がサポートされる。2次元以上のC配列型からのmdspan
構築はコンパイルエラーとなるか、要素アクセス時に未定義動作を引き起こす。配列先頭要素ポインタからの変換は意図しない結果をもたらす。
// 1次元配列からの変換 double arr1d[100] = /*...*/; // 1次元ビュー/固定サイズ100 using Array100 = std::mdspan<double, std::extents<size_t, 100>>; Array100 m1{arr1d}; // OK std::mdspan m2{arr1d}; // OK: CTAD // 1次元ビュー/動的サイズ100 using ArrayN = std::mdspan<double, std::dextents<size_t, 1>>; ArrayN m3{arr1d, 100}; // OK std::mdspan m4{arr1d, 100}; // OK: CTAD // 1次元配列要素先頭ポインタから変換(CTAD) std::mdspan m5{&arr1d[0], 100}; // OK: m4と等価 std::mdspan m6{&arr1d[0]}; // OK: 0次元ビュー(!) // m6はstd::mdspan<double, std::extents<size_t>>型に推論される
// 2次元配列からの変換 double arr2d[4][4] = /*...*/; // 2次元ビュー/サイズ4x4(?) using Mat4x4 = std::mdspan<double, std::extents<size_t, 4, 4>>; Mat4x4 m1{arr2d}; // NG: ill-formed Mat4x4 m2{arr2d, 4, 4}; // NG: ill-formed std::mdspan m3{arr2d}; // NG: ill-formed std::mdspan m4{arr2d, 4, 4}; // NG // プログラマの意図に反してm4は「要素型double[4]の2次元ビュー」 // std::mdspan<double[4], std::dextents<size_t, 2>>>型に推論され、 // double[4]型の4要素配列に対する2次元ビュー(4x4=16要素)を構築する。 // 例1: 要素代入 m4[0,0] = 3.14; は型不一致となるためill-formed // 例2: インデクス位置[0,0~3]以外は範囲外のためUBを引き起こす // 2次元配列先頭要素ポインタから変換(?) Mat4x4 m5{&arr2d[0][0]}; // NG std::mdspan m6{&arr2d[0][0], 4, 4}; // NG // m6はstd::mdspan<double, std::dextents<size_t, 2>>>型に推論される。 // double[4][4]型をdouble[16]型として扱うため厳密にはUBとなる。 // 詳細は提案文書 P2554R0 を参照。
次元情報指定コンストラクタ(CTAD)
メモリ領域ポインタとstd::extents
/std::dextents
*2を指定すると、任意の次元数(rank)と要素数(extents)をもつmdspan
型を推論可能となる。CTADを用いるメリットは小さいが、第1引数のポインタ型から要素型(mdspan::element_type
)が推論される。
double *ptr = /*...*/; // 2次元ビュー/動的サイズ2x3 using Extents2D = std::dextents<size_t, 2>; std::mdspan m1{ptr, Extents2D{2, 3}}; // OK // 2次元ビュー/動的+固定サイズNx3 using ExtentsNx3 = std::extents<size_t, std::dynamic_extent, 3>; std::mdspan m2{ptr, ExtentsNx3{2}}; // OK // 2次元ビュー/固定サイズ2x3 using Extents2x3 = std::extents<size_t, 2, 3>; std::mdspan m3{ptr, Extents2x3{}}; // OK
メモリレイアウト型変換
行優先(row major)std::layout_right
/列優先(column major)std::layout_left
レイアウトから汎用ストライドstd::layout_stride
レイアウトへは常に安全に変換可能。異種レイアウト間変換においては、マッピングのずらし幅(stride)に互換性があるケースに限って変換可能。
注:std::mdspan
は実データに対する「ビュー」にすぎない。例えば行列の転置(transpose)をmdspan
型変換のみで実現することはできない。
double *ptr = /*...*/; // 2次元ビュー/固定サイズ3x3 using Extents3x3 = std::extents<size_t, 3, 3>; using MatCpp = std::mdspan<double, Extents3x3 /*std::layout_right*/>; using MatFortran = std::mdspan<double, Extents3x3, std::layout_left>; using MatStrided = std::mdspan<double, Extents3x3, std::layout_stride>; // C++/Fortran互換→汎用レイアウト変換 MatCpp src_cpp{ptr}; MatFortran src_fortran{ptr}; MatStrided m1{src_cpp}; // OK MatStrided m2{src_fortran}; // OK // C++⇔Fortran互換レイアウト変換 MatFortran m3{src_cpp}; // NG MatCpp m4{src_fortran}; // NG // 汎用レイアウト→C++/Fortran互換レイアウト変換 std::array<int, 2> strides{3, 1}; std::layout_stride::mapping<Extents3x3> mapping{{}, strides}; MatStrided src_stride{ptr, mapping}; MatCpp m5{src_stride}; // OK MatFortran m6{src_stride}; // NG: 未定義動作(UB) // strides{3,1}のlayout_stride::mapping<Extents3x3>は // layout_left::mapping<Extents3x3>と互換性がないため、 // 事前条件(Preconditions)違反となってUBを引き起こす。
double *ptr = /*...*/; // 1次元ビュー/互換レイアウト(stride={1}) using Extents1D = std::extents<size_t, 10>; using ArrayRow = std::mdspan<double, Extents1D, std::layout_right>; using ArrayCol = std::mdspan<double, Extents1D, std::layout_left>; ArrayRow src_row{ptr}; ArrayCol src_col{ptr}; ArrayCol m1{src_row}; // OK ArrayRow m2{src_col}; // OK // 次元数1ではlayout_right/layout_leftレイアウトは同一