On 11/19/25 14:54, Tomasz Kaminski wrote:
On Tue, Nov 18, 2025 at 3:28 PM Luc Grosheintz <[email protected]>
wrote:

Implement submdspan_extents as described in P3663 and adds it to the std
module.

There's one deviation from the standard. Doesn't (under all
circumstances) require:

   0 <= begin[k] <= end[k] <= exts.extent(k)

where the k-th slice range is [begin[k], end[k]). Instead, it requires
that the k-th slice ranges is contained in the k-th extent interval. If
the the slice range is empty, then that condition is always satisfied,
even if

   begin[k] == end[k] > exts.extent(k)

This was always intended to work, so please submit the LWG issue.

Just to double check: what does "intended to work mean", in an
example:

auto exts = std::extents(3);
auto s1 = strided_slice{3, 0, std::cw<1>}; // valid
auto s2 = strided_slice{4, 0, std::cw<1>}; // also valid?
auto s3 = strided_slice{std::cw<4>, 0, std::cw<1>}; // not valid

My understanding is that `s2` is intended to be invalid. Then that's
how it's currently implemented and as was discussed in:

https://gcc.gnu.org/pipermail/libstdc++/2025-November/064213.html

The problem is that I don't fully know how to write it. To me
the easiest is:

   0 <= first_ <= last_ <= n

but P3663 intentionally moved away from that language.

We could remove the wording about the k-th extent interval
overlapping the k-th slice range; and replace with something
similar to valid slice type:

   (10.x) - if S is a specialization of strided_slice, then
   (10.x.1) + 0 <= s.offset <= x is true, and
   (10.x.2) + 0 <= s.extent <= x is true, and
   (10.x.3) + s.offset + s.extent <= x is true.

   (10.y) - if S is a collapsing slice type, then 0 <= s < x is
            true.

where the slice s is of type S and x is the k-th extent.

What document do we write the diff against? Not N5014, and to my
knowledge the next draft hasn't been published yet. (Probably
takes time to merge all papers into the standard.) Can it be against
P3663R3?


The deviation is that we enforce the above inequality through
preconditions. This is analogous to what the standard requires if
begin[k] is a constant wrapper.

         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 <[email protected]>
---
  libstdc++-v3/include/std/mdspan               | 135 ++++++++++++++
  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, 365 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 70656bcdd01..36e04f7e1b5 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -1003,20 +1003,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        __assert_valid_slices(const _Extents& __exts, const _Slices&...
__slices)
        {
         constexpr auto __rank = _Extents::rank();
         auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>)
         {
           ((__assert_valid_slice(__extract_extent<_Is>(__exts),
                                  __slices...[_Is])),...);
         };
         __impl(make_index_sequence<__rank>());
        }
+
+    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{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
    }

    template<typename _Extents>
      class layout_left::mapping
      {
      public:
        using extents_type = _Extents;
        using index_type = typename extents_type::index_type;
        using size_type = typename extents_type::size_type;
@@ -2678,20 +2702,131 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
               typename _MappingType::layout_type>;

    template<typename _MappingType, typename _AccessorType>
      mdspan(const typename _AccessorType::data_handle_type&, const
_MappingType&,
            const _AccessorType&)
      -> mdspan<typename _AccessorType::element_type,
               typename _MappingType::extents_type,
               typename _MappingType::layout_type, _AccessorType>;

  #if __glibcxx_submdspan
+  namespace __mdspan
+  {
+    template<typename _Slice>
+      consteval bool
+      __is_statically_empty_strided_slice()
+      {
+       if constexpr (__is_strided_slice<_Slice>)
+         {
+           using _ExtentType = typename _Slice::extent_type;
+           return __detail::__integral_constant_like<_ExtentType>

We do not need __intergral_constant_like (and the expressions matching)
for concepts, is_constant_wrapper should be sufficient. This is why the
normalization is in place.

+             && _ExtentType() == 0;
+         }
+       else
+         return false;
+      }
+
+    template<typename _Slice>
+      consteval bool
+      __is_statically_sized_strided_slice()
+      {
+       if constexpr (__is_strided_slice<_Slice>)
+         {
+           using _ExtentType = typename _Slice::extent_type;
+           using _StrideType = typename _Slice::stride_type;
+           return __detail::__integral_constant_like<_ExtentType>
+             && __detail::__integral_constant_like<_StrideType>;

Same here.

+         }
+       else
+         return false;
+      }
+
+    template<typename _IndexType, size_t _Extent, typename _Slice>
+      consteval size_t
+      __deduce_static_slice_extent()
+      {
+       if constexpr (same_as<_Slice, full_extent_t>)
+         return _Extent;
+       else if constexpr (__is_statically_empty_strided_slice<_Slice>())

+         return 0;
+       else if constexpr (__is_statically_sized_strided_slice<_Slice>())
+         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
+      __deduce_dynamic_slice_extent(const _Extents& __exts, _Slice
__slice)
+      {
+       using _IndexType = typename _Extents::index_type;
+       if constexpr (__is_strided_slice<_Slice>)
+         return __slice.extent == 0 ? 0 :
+           1 + (__slice.extent - 1) / __slice.stride;
+       else if constexpr (convertible_to<_Slice, _IndexType>)
+         return 1;
+       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::__deduce_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::__deduce_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... _Slices>
+    requires (sizeof...(_Slices) == sizeof...(_Extents))
+    constexpr auto
+    submdspan_extents(const extents<_IndexType, _Extents...>& __exts,
+                     _Slices... __raw_slices)
+    {
+      auto [...__slices] = submdspan_canonicalize_slices(__exts,
__raw_slices...);

I would much more prefer if we would not instantiate tuple here, as this is
compile
heavy, and completely unnecessary do:
   return __mdspan::__subextents(__exts, slice_cast(__slices)...);
And insert __mdspan::__assert_valid_slices(__exts, __slices...) into
__subextents.
This will apply to any other calls to canonicalize_slices in later patches.


+    }
+
    template<typename _IndexType, size_t... _Extents, typename... _Slices>
      requires (sizeof...(_Extents) == sizeof...(_Slices))
      constexpr auto
      submdspan_canonicalize_slices(const extents<_IndexType, _Extents...>&
__exts,
                                   _Slices... __raw_slices)
      {
        auto [...__slices]
         = make_tuple(__mdspan::__slice_cast<_IndexType>(__raw_slices)...);
        __mdspan::__assert_valid_slices(__exts, __slices...);
        return make_tuple(__slices...);
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
@@ -1870,27 +1870,30 @@ export namespace std
    using std::layout_right;
    using std::layout_stride;
    using std::default_accessor;
  #if __glibcxx_aligned_accessor
    using std::aligned_accessor;
  #endif
    using std::mdspan;
  #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

  // 20.2 <memory>
  export namespace std
  {
    using std::align;
    using std::allocator;
    using std::allocator_arg;
    using std::allocator_arg_t;
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
@@ -62,11 +62,20 @@ template<CustomIndexKind Kind, bool Copyable = false>
    };

  using IntLike = CustomIndexType<CustomIndexKind::Const>;
  using ThrowingInt = CustomIndexType<CustomIndexKind::Throwing>;
  using MutatingInt = CustomIndexType<CustomIndexKind::Mutating>;
  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.51.2




Reply via email to