[PATCH v3 2/2] libstdc++: Implement submdspan_extents. [PR110352]

Jonathan Wakely jwakely@redhat.com
Tue Dec 2 14:27:38 GMT 2025


On Mon, 01 Dec 2025 at 09:41 +0100, Luc Grosheintz wrote:
>Implement submdspan_extents as described in P3663 and adds it to the std
>module.

OK

>	PR libstdc++/110352
>
>libstdc++-v3/ChangeLog:
>
>	* include/std/mdspan (submdspan_extents): New function.
>	* src/c++23/std.cc.in: Add submdspan_extents.
>	* testsuite/23_containers/mdspan/int_like.h: Add StructuralInt.
>	* testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc: New test.
>	* testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc: New test.
>
>Signed-off-by: Luc Grosheintz <luc.grosheintz@gmail.com>
>---
> libstdc++-v3/include/std/mdspan               | 103 +++++++++++
> libstdc++-v3/src/c++23/std.cc.in              |   5 +-
> .../testsuite/23_containers/mdspan/int_like.h |   9 +
> .../mdspan/submdspan/submdspan_extents.cc     | 169 ++++++++++++++++++
> .../mdspan/submdspan/submdspan_extents_neg.cc |  48 +++++
> 5 files changed, 333 insertions(+), 1 deletion(-)
> create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc
> create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc
>
>diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan
>index 4fce6172589..31395b068b9 100644
>--- a/libstdc++-v3/include/std/mdspan
>+++ b/libstdc++-v3/include/std/mdspan
>@@ -865,6 +865,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	return extents<_IndexType, _Extents::static_extent(_Index)>{
> 	  __exts.extent(_Index)};
>       }
>+
>+    template<typename _IndexType, typename... _Slices>
>+      consteval auto
>+      __subrank()
>+      {
>+	return (static_cast<size_t>(!convertible_to<_Slices, _IndexType>)
>+		+ ... + 0);
>+      }
>+
>+    template<typename _IndexType, typename... _Slices>
>+      consteval auto
>+      __inv_map_rank()
>+      {
>+	constexpr auto __rank = sizeof...(_Slices);
>+	constexpr auto __sub_rank = __subrank<_IndexType, _Slices...>();
>+	auto __map = std::array<size_t, __sub_rank>{};
>+	auto __is_int_like = std::array<bool, __rank>{
>+	  convertible_to<_Slices, _IndexType>...};
>+
>+	size_t __i = 0;
>+	for (size_t __k = 0; __k < __rank; ++__k)
>+	  if (!__is_int_like[__k])
>+	    __map[__i++] = __k;
>+	return __map;
>+      }
> #endif // __glibcxx_submdspan
>   }
>
>@@ -2659,8 +2684,86 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	};
> 	__impl(make_index_sequence<__rank>());
>       }
>+
>+    template<typename _IndexType, size_t _Extent, typename _Slice>
>+      consteval size_t
>+      __static_slice_extent()
>+      {
>+	if constexpr (same_as<_Slice, full_extent_t>)
>+	  return _Extent;
>+	else if constexpr (same_as<_Slice, constant_wrapper<_IndexType(0)>>)
>+	  return 0;
>+	else if constexpr (__is_constant_wrapper<typename _Slice::extent_type>
>+			&& __is_constant_wrapper<typename _Slice::stride_type>)
>+	  return 1 + ((typename _Slice::extent_type{}) - 1)
>+		   / (typename _Slice::stride_type{});
>+	else
>+	  return dynamic_extent;
>+      }
>+
>+    template<size_t _K, typename _Extents, typename _Slice>
>+      constexpr typename _Extents::index_type
>+      __dynamic_slice_extent(const _Extents& __exts, _Slice __slice)
>+      {
>+	if constexpr (__is_strided_slice<_Slice>)
>+	  return __slice.extent == 0 ? 0 : 1 + (__slice.extent - 1) / __slice.stride;
>+	else
>+	  return __exts.extent(_K);
>+      }
>+
>+    template<typename _IndexType, size_t... _Extents, typename... _Slices>
>+      requires (sizeof...(_Slices) == sizeof...(_Extents))
>+      constexpr auto
>+      __subextents(const extents<_IndexType, _Extents...>& __exts,
>+		   _Slices... __slices)
>+      {
>+	constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType, _Slices...>();
>+	auto __impl = [&]<size_t... _Indices>(index_sequence<_Indices...>)
>+	{
>+	  using _SubExtents = extents<_IndexType,
>+	      (__mdspan::__static_slice_extent<_IndexType,
>+		 _Extents...[__inv_map[_Indices]],
>+		 _Slices...[__inv_map[_Indices]]>())...>;
>+	  if constexpr (_SubExtents::rank_dynamic() == 0)
>+	    return _SubExtents{};
>+	  else
>+	    {
>+	      using _StaticSubExtents = __mdspan::_StaticExtents<
>+		__mdspan::__static_extents<_SubExtents>()>;
>+	      auto __create = [&]<size_t... _Is>(index_sequence<_Is...>)
>+	      {
>+		constexpr auto __slice_idx = [__inv_map](size_t __i) consteval
>+		{
>+		  return __inv_map[_StaticSubExtents::_S_dynamic_index_inv(__i)];
>+		};
>+
>+		return _SubExtents{
>+		  (__mdspan::__dynamic_slice_extent<__slice_idx(_Is)>(
>+		     __exts, __slices...[__slice_idx(_Is)]))...};
>+	      };
>+	      constexpr auto __dyn_subrank = _SubExtents::rank_dynamic();
>+	      return __create(make_index_sequence<__dyn_subrank>());
>+	    }
>+	};
>+
>+	return __impl(make_index_sequence<__inv_map.size()>());
>+      }
>   }
>
>+  template<typename _IndexType, size_t... _Extents, typename... _RawSlices>
>+    requires (sizeof...(_RawSlices) == sizeof...(_Extents))
>+    constexpr auto
>+    submdspan_extents(const extents<_IndexType, _Extents...>& __exts,
>+		      _RawSlices... __raw_slices)
>+    {
>+      auto __impl = [&__exts](auto... __slices)
>+      {
>+	__mdspan::__check_valid_slices(__exts, __slices...);
>+	return __mdspan::__subextents(__exts, __slices...);
>+      };
>+      return __impl(__mdspan::__slice_cast<_IndexType>(__raw_slices)...);
>+    }
>+
>   template<typename _IndexType, size_t... _Extents, typename... _RawSlices>
>     requires (sizeof...(_Extents) == sizeof...(_RawSlices))
>     constexpr auto
>diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/std.cc.in
>index 63b3d183bf5..c2a9293b05a 100644
>--- a/libstdc++-v3/src/c++23/std.cc.in
>+++ b/libstdc++-v3/src/c++23/std.cc.in
>@@ -1877,13 +1877,16 @@ export namespace std
> #if __glibcxx_padded_layouts
>   using std::layout_left_padded;
>   using std::layout_right_padded;
>+#endif
>+#if __glibcxx_submdspan
>   using std::strided_slice;
>   using std::full_extent_t;
>   using std::full_extent;
>   using std::submdspan_mapping_result;
>   using std::submdspan_canonicalize_slices;
>+  using std::submdspan_extents;
> #endif
>-  // FIXME submdspan_extents, mdsubspan
>+  // FIXME mdsubspan
> }
> #endif
>
>diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
>index 2f79b9dd3b5..b839be73ae9 100644
>--- a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
>+++ b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
>@@ -69,4 +69,13 @@ using RValueInt = CustomIndexType<CustomIndexKind::RValue>;
> struct NotIntLike
> { };
>
>+struct StructuralInt
>+{
>+  constexpr
>+  operator int() const noexcept
>+  { return value; }
>+
>+  int value;
>+};
>+
> #endif // TEST_MDSPAN_INT_LIKE_H
>diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc
>new file mode 100644
>index 00000000000..841910a77c8
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc
>@@ -0,0 +1,169 @@
>+// { dg-do run { target c++26 } }
>+#include <mdspan>
>+
>+#include "../int_like.h"
>+#include <testsuite_hooks.h>
>+
>+constexpr size_t dyn = std::dynamic_extent;
>+constexpr auto all = std::full_extent;
>+
>+constexpr bool
>+test_from_full_extent()
>+{
>+  auto exts = std::extents<int, 3, dyn, 7>{};
>+  auto sub_exts = submdspan_extents(exts, 1, all, all);
>+  VERIFY(sub_exts.rank() == 2);
>+  VERIFY(sub_exts.static_extent(0) == dyn);
>+  VERIFY(sub_exts.extent(0) == exts.extent(1));
>+  VERIFY(std::cmp_equal(sub_exts.static_extent(1), exts.extent(2)));
>+  return true;
>+}
>+
>+template<template<typename, typename> typename Pair, template<int> typename Cw>
>+  constexpr bool
>+  test_from_tuple()
>+  {
>+    auto exts = std::extents<int, 3, 5, 7>{};
>+    auto s0 = Cw<1>{};
>+    auto s1 = Pair{Cw<1>{}, Cw<2>{}};
>+    auto s2 = Pair{Cw<1>{}, 4};
>+    auto sub_exts = submdspan_extents(exts, s0, s1, s2);
>+    VERIFY(sub_exts.rank() == 2);
>+    VERIFY(sub_exts.static_extent(0) == size_t(get<1>(s1) - get<0>(s1)));
>+    VERIFY(sub_exts.static_extent(1) == dyn);
>+    VERIFY(std::cmp_equal(sub_exts.extent(1), get<1>(s2) - get<0>(s2)));
>+    return true;
>+  }
>+
>+template<template<int> typename Cw>
>+  constexpr bool
>+  test_from_tuple_all()
>+  {
>+    test_from_tuple<std::tuple, Cw>();
>+    test_from_tuple<std::pair, Cw>();
>+    return true;
>+  }
>+
>+
>+template<typename Int>
>+  void
>+  test_from_int_like_as_scalar()
>+  {
>+    auto exts = std::extents<int, 3, 5>{};
>+    auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{1, 3});
>+    VERIFY(sub_exts.rank() == 1);
>+    VERIFY(sub_exts.static_extent(0) == dyn);
>+    VERIFY(sub_exts.extent(0) == 2);
>+  }
>+
>+template<template<int> typename Cw>
>+  constexpr bool
>+  test_from_const_int()
>+  {
>+    auto exts = std::extents<int, 3, 5>{};
>+    auto sub_exts = submdspan_extents(exts, Cw<1>{}, std::tuple{1, 3});
>+    VERIFY(sub_exts.rank() == 1);
>+    VERIFY(sub_exts.static_extent(0) == dyn);
>+    VERIFY(sub_exts.extent(0) == 2);
>+    return true;
>+  }
>+
>+template<typename Int>
>+  constexpr bool
>+  test_from_int_like_in_tuple()
>+  {
>+    auto exts = std::extents<int, 3, 5>{};
>+    auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{Int(1), Int(3)});
>+    VERIFY(sub_exts.rank() == 1);
>+    VERIFY(sub_exts.static_extent(0) == dyn);
>+    VERIFY(sub_exts.extent(0) == 2);
>+    return true;
>+  }
>+
>+template<template<int> typename Cw>
>+  constexpr bool
>+  test_from_strided_slice()
>+  {
>+    auto exts = std::extents<int, 5, 7, 11>{};
>+    {
>+      auto s0 = 1;
>+      auto s1 = std::strided_slice{0, 0, 0};
>+      auto s2 = std::strided_slice{1, Cw<0>{}, 0};
>+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
>+      VERIFY(sub_exts.rank() == 2);
>+      VERIFY(sub_exts.static_extent(0) == dyn);
>+      VERIFY(sub_exts.extent(0) == 0);
>+      VERIFY(sub_exts.static_extent(1) == 0);
>+    }
>+
>+    {
>+      auto s0 = 1;
>+      auto s1 = std::strided_slice{0, 2, Cw<1>{}};
>+      auto s2 = std::strided_slice{1, Cw<2>{}, 1};
>+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
>+      VERIFY(sub_exts.rank() == 2);
>+      VERIFY(sub_exts.static_extent(0) == dyn);
>+      VERIFY(sub_exts.extent(0) == 2);
>+      VERIFY(sub_exts.static_extent(1) == dyn);
>+      VERIFY(sub_exts.extent(1) == 2);
>+    }
>+
>+    {
>+      // selected = 1 x [1, 3] x [1, 4, 7, 10]
>+      auto s0 = 1;
>+      auto s1 = std::strided_slice{1, Cw<4>{}, 2};
>+      auto s2 = std::strided_slice{1, Cw<10>{}, Cw<3>{}};
>+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
>+      VERIFY(sub_exts.rank() == 2);
>+      VERIFY(sub_exts.static_extent(0) == dyn);
>+      VERIFY(sub_exts.extent(0) == 2);
>+      VERIFY(sub_exts.static_extent(1) == 4);
>+    }
>+
>+    {
>+      // selected = [0, 2] x [1, 3] x [0, 3, 6]
>+      auto s0 = std::strided_slice(0, 3, 2);
>+      auto s1 = std::strided_slice(1, 4, 2);
>+      auto s2 = std::strided_slice(0, 7, 3);
>+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
>+      VERIFY(sub_exts.rank() == 3);
>+      VERIFY(sub_exts.extent(0) == 2);
>+      VERIFY(sub_exts.extent(1) == 2);
>+      VERIFY(sub_exts.extent(2) == 3);
>+    }
>+    return true;
>+  }
>+
>+template<int Value>
>+  using CW = std::constant_wrapper<Value, int>;
>+
>+template<int Value>
>+  using IC = std::integral_constant<int, Value>;
>+
>+constexpr bool
>+test_all()
>+{
>+  test_from_full_extent();
>+  test_from_tuple_all<CW>();
>+  test_from_tuple_all<IC>();
>+  test_from_const_int<CW>();
>+  test_from_const_int<IC>();
>+  test_from_strided_slice<CW>();
>+  test_from_strided_slice<IC>();
>+  test_from_int_like_in_tuple<StructuralInt>();
>+  return true;
>+}
>+
>+int
>+main()
>+{
>+  test_all();
>+  static_assert(test_all());
>+
>+  test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::Const, true>>();
>+  test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::Throwing, true>>();
>+  test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::Mutating, true>>();
>+  test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::RValue, true>>();
>+  test_from_int_like_as_scalar<StructuralInt>();
>+  return 0;
>+}
>diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc
>new file mode 100644
>index 00000000000..cf27c0c7e4d
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc
>@@ -0,0 +1,48 @@
>+// { dg-do compile { target c++26 } }
>+#include <mdspan>
>+
>+#include <cstdint>
>+
>+struct NotASlice
>+{ };
>+
>+constexpr bool
>+test_unrelated_stride_type()
>+{
>+  auto exts = std::extents(3, 5, 7);
>+  auto sub_exts = submdspan_extents(exts, 1, NotASlice{}, 2);  // { dg-error "required from" }
>+  return true;
>+}
>+static_assert(test_unrelated_stride_type());
>+
>+constexpr bool
>+test_invalid_stride_zero()
>+{
>+  auto exts = std::extents(3, 5, 7);
>+  auto s = std::strided_slice{0, 1, 0};
>+  auto sub_exts = submdspan_extents(exts, 1, s, 2);  // { dg-error "expansion of" }
>+  return true;
>+}
>+static_assert(test_invalid_stride_zero());
>+
>+template<typename Slice>
>+constexpr bool
>+test_out_of_bounds(const Slice& slice)
>+{
>+  auto exts = std::extents<uint16_t, 3, 5, 7>{};
>+  auto sub_exts = submdspan_extents(exts, 1, slice, 2);  // { dg-error "expansion of" }
>+  return true;
>+}
>+static_assert(test_out_of_bounds(std::strided_slice{0, 6, 1}));  // { dg-error "expansion of" }
>+static_assert(test_out_of_bounds(std::strided_slice{0, 7, 2}));  // { dg-error "expansion of" }
>+static_assert(test_out_of_bounds(std::strided_slice{1, 6, 1}));  // { dg-error "expansion of" }
>+static_assert(test_out_of_bounds(std::strided_slice{1, 6, 2}));  // { dg-error "expansion of" }
>+static_assert(test_out_of_bounds(std::tuple{1, 6}));             // { dg-error "expansion of" }
>+static_assert(test_out_of_bounds(std::tuple{std::cw<1>, std::cw<6>})); // { dg-error "expansion of" }
>+static_assert(test_out_of_bounds(std::strided_slice{-1, 2, 1})); // { dg-error "expansion of" }
>+static_assert(test_out_of_bounds(std::tuple{-1, 2}));            // { dg-error "expansion of" }
>+
>+// { dg-prune-output "cannot decompose class type 'NotASlice'" }
>+// { dg-prune-output "static assertion failed" }
>+// { dg-prune-output "__glibcxx_assert_fail" }
>+// { dg-prune-output "non-constant condition" }
>-- 
>2.52.0
>
>



More information about the Libstdc++ mailing list