yohhoyの日記

技術的メモをしていきたい日記

std::submdspan関数

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*/;
}

2026-05-15追記:提案文書P3982R2の採択により、1) strided_slice削除とextent_slice追加、2) range_slice追加が行われる。前者はstrided_sliceextent_slice名称変更かつextentフィールドの定義変更とも解釈可能。

まとめ:

  • 多次元ビューstd::mdspan(→id:yohhoy:20230303)から部分ビューを取り出す(slice)関数。
  • 第2引数以降のスライス指定子リストにより、各次元のスライス方法(slicing)を指定する。
    • 単一インデクス:単一値。指定次元に対するインデクスを固定し、次元数(rank)を1つ削減する。*1
    • インデクス範囲:開始位置(begin)+終了位置(end)の組。std::pairや2要素std::tuple等により*2、指定次元に対するインデクス範囲を取り出す。
    • ストライド指定範囲:オフセット位置(offset)+要素数(extent)+ストライド(stride)の組。std::extent_sliceにより、指定次元に対してずらし幅指定したインデクス範囲を取り出す。
      • strided_slice::extentextent_slice::extentフィールドの意味は異なることに注意[P3982R2]
      • 他プログラミング言語では一般的な*3、先頭位置(first)+終了位置(last)+ストライド(stride)の組による指定std::range_sliceも追加サポート[P3982R2]
    • 全選択:タグ値std::full_extent。指定次元をそのまま取り出す。
  • “ストライド指定範囲” 用のextent_slice型、range_slice型は名前付き初期化(→id:yohhoy:20170820)をサポートする[P3982R2]
    • 類似機能を提供するPython/numpyやMatlab/Fortranと異なり、strided_sliceは変換元の終了位置(end)ではなく変換元の要素数(extent)による指定を行う。
    • extent_sliceでは変換先の要素数(extent)を、range_sliceでは変換元の終了位置(last)を指定する。[P3982R2]
    • 例:extent_slice{.offset=1, .extent=4, .stride=3}またはextent_slice{1, 4, 3}
    • extent_slice{.offset=1, .extent=4, .stride=3}range_slice{.first=1, .last=11, .stride=3}は等価。[P3982R2]
    • 2024-12-08追記:提案文書P3355R1が採択され、strideに定数値1を指定したストライド指定範囲はunit-stride sliceとして特別扱いされる。インデクス範囲指定や全選択(std::full_extent)と同様にレイアウトポリシー維持基準に影響する。
  • “インデクス範囲” および “ストライド指定範囲” で用いるインデクス値は、通常の値と整数定数の2種類をサポートする。*4
    • 戻り値型mdspan<T,E,L,A>における多次元インデクス型Eの各次元要素数に影響を与える。
    • 通常の値:整数リテラル(literal)*5を含む整数値。指定次元の要素数は実行時(std::dynamic_extent)に決定する。
    • 整数定数:std::integral_constant互換の定数値*6。指定次元の要素数はコンパイル時に決定する。
  • レイアウトポリシー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_stridelayout_strideのまま。
    • ユーザ定義レイアウトポリシーをサポートするには、カスタマイズポイントsubmdspan_mapping関数を実装する。カスタマイズポイント実装は必須要件ではないが、汎用のフォールバック実装は提供されない。
    • 2026-03-31追記:提案文書P3663R3が採択され、std::submdspan利用者向けの柔軟なスライス方法指定とカスタマイズポイント実装者向けの正規化されたスライス指定子リストが分離された。
  • 要素型TとアクセスポリシーAは原則維持される。*7

スライス指定の例

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次元部分ビュー[P3982R2]
auto m5d = std::submdspan(m0,
  std::extent_slice{.offset=0, .extent=2, .stride=2},
  std::extent_slice{.offset=1, .extent=2, .stride=3});
//  -  2  -  -  5
//  -  -  -  -  -
//  - 12  -  - 15
// Extents = std::dextents<size_t, 2>

// 2x2要素の2次元部分ビュー[P3982R2]
auto m6d = std::submdspan(m0,
  std::range_slice{.first=0, .last=3, .stride=2},
  std::range_slice{.first=1, .last=5, .stride=3});
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次元部分ビュー[P3982R2]
auto m5s = std::submdspan(m0,
  std::extent_slice{.offset=0, .extent=Int<2>, .stride=Int<2>},
  std::extent_slice{.offset=1, .extent=Int<2>, .stride=Int<3>});
//  -  2  -  -  5
//  -  -  -  -  -
//  - 12  -  - 15
// Extents = std::extents<size_t, 2, 2>
// extent_slice::offset型はsubmdspan適用後の型に影響しない

2025-09-11追記:上記コードの整数定数Int<N>は、C++2c採択済みP2781R9で追加されるstd::cw<N>にて代替可能。

レイアウトポリシー変換

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要素ペアとしてアクセス可能な型を広くサポートする。

*3:Fortranの arr(first:last:step)、Python/numpyの arr[first:last:step]、Matlabの arr(first:step:last) に相当。

*4:“単一インデクス” も整数定数をサポートするが、その効果は通常の値を指定したときと同じ。

*5:戻り値型の決定で利用されるため、std::integral_constant<int,N>::value のように値が型情報にエンコードされていないと、コンパイル時にその値(value)へアクセスできない。

*6:データメンバ value で定数値アクセス可能な型(integral-constant-like)を広くサポートする。

*7:厳密には submdspan 関数適用後 mdspan<T,E,L,A> の要素型 T やアクセスポリシー A は A::offset_policy 型に依存する。通常は A::offset_policy == A として定義されるため、テンプレートパラメータ T, A 型が変化するケースは稀。