C++2c(C++26)標準ライブラリに追加される多次元部分ビューstd::submdspan
について。
// <mdspan>ヘッダ namespace std { template< class T, class E, class L, class A, class... SliceSpecifiers> constexpr auto submdspan( const mdspan<T, E, L, A>& src, SliceSpecifiers... slices) -> /*see below*/; }
まとめ:
- 多次元ビュー
std::mdspan
(→id:yohhoy:20230303)から部分ビューを取り出す(slice)関数。 - 第2引数以降のスライス指定子リストにより、各次元のスライス方法(slicing)を指定する。
- “ストライド指定範囲” 用の
strided_slice
型は名前付き初期化(→id:yohhoy:20170820)をサポートする。- 類似機能を提供するPython/numpyやMatlab/Fortranと異なり、変換元における終了位置(end)ではなく変換元における要素数(extent)による指定を行う。
- 例:
strided_slice{.offset=1, .extent=10, .stride=3}
またはstrided_slice{1, 10, 3}
- 2024-12-08追記:提案文書P3355R1が採択され、stride==1な
strided_slice
はunit-stride sliceとして特別扱いされる。インデクス範囲指定や全選択(std::full_extent
)と同様にレイアウトポリシー維持基準に影響する。
- “インデクス範囲” および “ストライド指定範囲” で用いるインデクス値は、通常の値と整数定数の2種類をサポートする。*3
- レイアウトポリシー
L
はC++標準ライブラリ提供メモリレイアウトのみサポートする。- 2024-08-05追記:
std::layout_right_padded
,std::layout_left_padded
追加にともない変換ルールも複雑化している。更新版は id:yohhoy:20240805 参照。 std::layout_right
:同ポリシー型を維持できる場合はlayout_right
を利用。それ以外はlayout_stride
へ変換。std::layout_left
:同ポリシー型を維持できる場合はlayout_left
を利用。それ以外はlayout_stride
へ変換。std::layout_stride
:layout_stride
のまま。- ユーザ定義レイアウトポリシーをサポートするには、カスタマイズポイント
submdspan_mapping
関数を実装する。カスタマイズポイント実装は必須要件ではないが、汎用のフォールバック実装は提供されない。
- 2024-08-05追記:
- 要素型
T
とアクセスポリシーA
は原則維持される。*6
スライス指定の例
int a[15]; // {1, 2, ... 15} std::ranges::iota(a, 1); // 3x5要素の2次元ビュー std::mdspan m0{a, std::extents<size_t, 3, 5>{}}; // 1 2 3 4 5 // 6 7 8 9 10 // 11 12 13 14 15 auto m1 = std::submdspan(m0, 1, std::full_extent); // [6 7 8 9 10] (5要素1次元) auto m2 = std::submdspan(m0, std::full_extent, 2); // [3 8 13] (3要素1次元) auto m3 = std::submdspan(m0, 1, 2); // 8 (0次元) // 2x3要素の2次元部分ビュー auto m4d = std::submdspan(m0, std::pair{1,2}, std::tuple{1,3}); // - - - - - // - 7 8 9 - // - 12 13 14 - // Extents = std::dextents<size_t, 2> // 2x2要素の2次元部分ビュー auto m5d = std::submdspan(m0, std::strided_slice{.offset=0, .extent=3, .stride=2}, std::strided_slice{.offset=1, .extent=4, .stride=3}); // - 2 - - 5 // - - - - - // - 12 - - 15 // Extents = std::dextents<size_t, 2>
template <int N> constexpr auto Int = std::integral_constant<int, N>{}; // 2x3要素(静的要素数)の2次元部分ビュー auto m4s = std::submdspan(m0, std::pair{Int<1>,Int<2>}, std::tuple{Int<1>,Int<3>}); // - - - - - // - 7 8 9 - // - 12 13 14 - // Extents = std::extents<size_t, 2, 3> // 2x2要素(静的要素数)の2次元部分ビュー auto m5s = std::submdspan(m0, std::strided_slice{.offset=0, .extent=Int<3>, .stride=Int<2>}, std::strided_slice{.offset=1, .extent=Int<4>, .stride=Int<3>}); // - 2 - - 5 // - - - - - // - 12 - - 15 // Extents = std::extents<size_t, 2, 2> // strided_slice::offset型はsubmdspan適用後の型に影響しない
レイアウトポリシー変換
int a[60] = /*...*/; // 3x4x5要素の3次元ビュー(LayoutPolicy=layout_right) std::mdspan m0{a, std::extents<size_t, 3, 4, 5>{}}; auto m1 = std::submdspan(m0, 1, std::full_extent, std::full_extent); // LayoutPolicy = layout_right (4x5要素2次元) auto m2 = std::submdspan(m0, 1, std::pair{1,2}, std::full_extent); // LayoutPolicy = layout_right (2x5要素2次元) auto m3 = std::submdspan(m0, 1, 0, 2); // LayoutPolicy = layout_right (0次元) auto m4 = std::submdspan(m0, std::full_extent, 0, std::full_extent); // LayoutPolicy = layout_stride (3x5要素2次元) // 変換後m4の strides[] = {20, 1}
// 3x4x5要素の3次元ビュー, layout_stride = {20, 1, 4} using Exts3x4x5 = std::extents<size_t, 3, 4, 5>; std::array strides = {20, 1, 4}; auto mapping = std::layout_stride::mapping{Exts3x4x5{}, strides}; std::mdspan m0s{a, mapping}; auto m5 = std::submdspan(m0s, 0, std::full_extent, 0); // LayoutPolicy = layout_stride (4要素1次元) assert(m5.mapping().stride(0) == 1); // メモリレイアウト的にはlayout_right互換となる // コンパイル時の型計算ではstridesアクセスできないため // layout_strideからは常にlayout_strideへと変換される // 変換コンストラクタによりlayout_rightへ明示変換可能 std::mdspan<int, std::extents<size_t, 4>> m5r{ m5 }; // LayoutPolicy = layout_right (4要素1次元)
関連URL
*1:1次元 mdspan からは 0次元 mdspan(→id:yohhoy:20230309)が生成される。
*2:2要素ペアとしてアクセス可能な型(index-pair-like)を広くサポートする。
*3:“単一インデクス” も整数定数をサポートするが、その効果は通常の値を指定したときと同じ。
*4:戻り値型の決定で利用されるため、std::integral_constant<int,N>::value のように値が型情報にエンコードされていないと、コンパイル時にその値(value)へアクセスできない。
*5:データメンバ value で定数値アクセス可能な型(integral-constant-like)を広くサポートする。
*6:厳密には submdspan 関数適用後 mdspan<T,E,L,A> の要素型 T やアクセスポリシー A は A::offset_policy 型に依存する。通常は A::offset_policy == A として定義されるため、テンプレートパラメータ T, A 型が変化するケースは稀。