On 11/20/25 17:08, Tomasz Kaminski wrote:
On Tue, Nov 18, 2025 at 3:37 PM Luc Grosheintz <[email protected]>
wrote:

Implements `submdspan` and `submdspan_mapping` for layout_left as
described in P3663 (Future proofing mdspan).

When computing the offset of the submdspan, one must check that the
lower bound of the slice range isn't out-of-range. There's a few
cases when the lower bound is never out-of-range:

   - full_extent and exts.extent(k) != 0,
   - collapsing slice types.

If those conditions are known to hold, no checks are generated.


Similarly, if all slices are full_extent, there's no need to call
mapping(0,...,0) for standardized mappings.

The implementation prepares to use the symmetry between layout_left and
layout_right and introduces concepts like a "layout side", i.e. left,
right or unknown/strided.

The tests use an iterator to replace nested for-loops. Which also makes
it easier to write the core test logic in a rank-independent manner.

         PR libstdc++/110352

libstdc++-v3/ChangeLog:

         * include/std/mdspan (layout_left::mapping::submdspan_mapping):
         New friend function.
         (submdspan): New function.
         * src/c++23/std.cc.in: Add submdspan.
         * testsuite/23_containers/mdspan/submdspan/submdspan.cc: New test.
         * testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc:
New test.
         * testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc: New
test.

Signed-off-by: Luc Grosheintz <[email protected]>

Posted some idea on how to classify the slices without so much
metaprogramming,
the idea would be to have function, that would have constexpr array of
slice kind,
would go over it either left to right or right to left, and return if we
have matching
blocks. Could accept rank as a parameter. We most likely want this function
to simply
accept span, subrank, and direction, and not be templated.

Could you let me know what you think about the idea?
I think I will stop review here, as this will affect the rest of the code.

I have a version drafted up: it works reasonably well, only I'd rather
reverse the array of SliceKind than pass in a direction. It feels easier
to express: same as left; but from the back. Makes it explicit that there
is no difference between left and right. Since, it's all consteval, there's
no cost at runtime; and at compile time we save instantiating once for
each direction.

---------------

Should all standardized mapping have `_M_strides`? I don't object, but
the usage is slightly different: _M_strides creates an array of length
`rank`; while the loop in question only needs an array of length
`subrank`, it only needs the strides at non-collapsing slices.

---------------

About qualified calls (to avoid ADL). Is this also required for uglified
names, e.g. __subextents? Since users aren't permitted to use those names
they can't cause ADL-related issues.

If we know that the types of all arguments are "ours", i.e. defined in
std:: or builtins like `int`, do we still need to protect against ADL?
After canonicalization, we're left with: standarsized mapping, extents,
full_extent_t, strided_slice, constant_wrapper and integers.

Sofar I've been implementing everything as if the answer were: "no" to
both questions. If that's wrong, let me know, and I'll do a pass to
clean up all of <mdspan>.



---
  libstdc++-v3/include/std/mdspan               | 387 ++++++++++++++++++
  libstdc++-v3/src/c++23/std.cc.in              |   2 +-
  .../mdspan/submdspan/submdspan.cc             | 369 +++++++++++++++++
  .../mdspan/submdspan/submdspan_mapping.cc     | 136 ++++++
  .../mdspan/submdspan/submdspan_neg.cc         | 102 +++++
  5 files changed, 995 insertions(+), 1 deletion(-)
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan.cc
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc

diff --git a/libstdc++-v3/include/std/mdspan
b/libstdc++-v3/include/std/mdspan
index 36e04f7e1b5..712826ea7e7 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -578,20 +578,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
           return __r == 0 ? 1 : __exts.extent(0);
         else if constexpr
(__all_dynamic(std::span(__sta_exts).first(__rank-1)))
           return __extents_prod(__exts, 1, 0, __r);
         else
           {
             size_t __sta_prod = __fwd_partial_prods<__sta_exts>[__r];
             return __extents_prod(__exts, __sta_prod, 0, __r);
           }
        }

+    template<typename _IndexType, size_t _Nm>
+      consteval _IndexType
+      __fwd_prod(span<const _IndexType, _Nm> __values)
+      {
+       _IndexType __ret = 1;
+       for(auto __value : __values)
+         __ret *= __value;
+       return __ret;
+      }
+
      // Preconditions: _r < _Extents::rank()
      template<typename _Extents>
        constexpr typename _Extents::index_type
        __rev_prod(const _Extents& __exts, size_t __r) noexcept
        {
         constexpr size_t __rank = _Extents::rank();
         constexpr auto& __sta_exts = __static_extents<_Extents>();
         if constexpr (__rank == 1)
           return 1;
         else if constexpr (__rank == 2)
@@ -1027,20 +1037,374 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         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;
        }
+
+    template<typename _IndexType, typename _Slice>
+      constexpr _IndexType
+      __slice_begin(_Slice __slice)
+      {

  Please consider implementing this as:
    if constexpr (is_same_v<_Slice, _full_extent_t>)
       return 0;
    else if constexpr (__is_strided_slice<_Slice>)
       return __slice.offset;
    else
       return __slice;
This should be lighter to check that using concept,
and you do not need to pass _IndexType.


+       if constexpr (convertible_to<_Slice, _IndexType>)
+         return __slice;
+       else if constexpr (__is_strided_slice<_Slice>)
+         return __slice.offset;
+       else
+         return 0; // full_extent
+      }
+
+    template<typename _Mapping, typename... _Slices>
+      constexpr size_t
+      __suboffset(const _Mapping& __mapping, const _Slices&... __slices)
+      {
+       using _IndexType = typename _Mapping::index_type;
+       auto __any_past_the_end = [&]<size_t...
_Is>(index_sequence<_Is...>)
+       {

+         auto __is_past_the_end = [](const auto& __slice, const auto&
__ext)
+         {
+           using _Slice = remove_cvref_t<decltype(__slice)>;

     if constexpr (__ext.static_extent(


I will suggest expressing this as:
        if constexpr (is_same_v<_Slice, __full_extent_t>)
          return true; // we checked that before
        else if constexpr (!_is_strided_slice<_Slice>)
          return false;
        else
           // Compiler wii probably optimize if this two are constant.
          return __slice.offset = __ext.extent(0);


+           if constexpr (is_convertible_v<_Slice, _IndexType>)

I do not think this is correct, we single indices to operators.

+             return false;
+           else if (same_as<_Slice, full_extent_t>
+               && __ext.static_extent(0) > 0
+               && __ext.static_extent(0) != dynamic_extent)


+             return false;
+           else
+             return __slice_begin<_IndexType>(__slice) == __ext.extent(0);

We seem to this for __full_extent, before the previous if is not constexpr.

+         };
+
+         const auto& __exts = __mapping.extents();

+         return ((__is_past_the_end(__slices...[_Is],
+                                    __extract_extent<_Is>(__exts))) ||
...);
+       };
+
+       if constexpr ((same_as<_Slices, full_extent_t> && ...))
+         return __offset(__mapping);
+

I think I would check empty mdspan separately here, so if
empty(__mapping.extents())
the return __mapping.required_span_size(), this will also simply
__any_past_the_end.


+       if constexpr (!((convertible_to<_Slices, _IndexType>) && ...))

+         if
(__any_past_the_end(make_index_sequence<sizeof...(__slices)>()))
+           return __mapping.required_span_size();
+       return __mapping(__slice_begin<_IndexType>(__slices)...);

Because we do not pass _IndexType to slice begin, use
static_cast<_IndexType> here.

+      }
+
+    enum class _LayoutSide
+    {
+      __left,
+      __right,
+      __unknown
+    };
+
+    template<typename _Mapping>
+      consteval _LayoutSide
+      __deduce_mapping_side()
+      {
+       if constexpr (__is_left_padded_mapping<_Mapping>
+           || __mapping_of<layout_left, _Mapping>)
+         return _LayoutSide::__left;
+       if constexpr (__is_right_padded_mapping<_Mapping>
+           || __mapping_of<layout_right, _Mapping>)
+         return _LayoutSide::__right;
+       else
+         return _LayoutSide::__unknown;
+      }
+
+    template<_LayoutSide _Side, size_t _Rank>
+      struct _StridesTrait
+      {
+       static constexpr const _LayoutSide _S_side = _Side;
+
+       static constexpr size_t
+       _S_idx(size_t __k) noexcept
+       {
+         if constexpr (_Side == _LayoutSide::__left)
+           return __k;
+         else
+           return _Rank - 1 - __k;
+       }
+
+       template<typename _Mapping>
+         static constexpr typename _Mapping::index_type
+         _S_extent(const _Mapping& __mapping, size_t __k)
+         {
+           if (__k == 0)
+             return __mapping.stride(_S_idx(1));

Nice trick of handling padded and not padded, but comment on it.

+           else
+             return __mapping.extents().extent(_S_idx(__k));
+         }
+
+       template<typename _IndexType, typename... _Slices>
+         static consteval auto
+         _S_inv_map()
+         {
+           static_assert(_Side != _LayoutSide::__unknown);
+           auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>)
+           {
+             return __inv_map_rank<_IndexType,
_Slices...[_S_idx(_Is)]...>();
+           };
+           return __impl(make_index_sequence<_Rank>());
+         }
+      };
+
+    template<typename _SubExtents, typename _Mapping, typename... _Slices>
+      constexpr auto
+      __substrides_generic(const _Mapping& __mapping, const _Slices&...
__slices)
+      {
+       using _IndexType = typename _Mapping::index_type;
+       if constexpr (_SubExtents::rank() == 0)
+         return array<_IndexType, _SubExtents::rank()>{};
+       else
+         {
+           auto __stride = [&__mapping](size_t __k, auto __slice) ->
_IndexType
+           {
+             if constexpr (__is_strided_slice<decltype(__slice)>)
+               if (__slice.stride < __slice.extent)
+                 return __mapping.stride(__k) * __slice.stride;
+             return __mapping.stride(__k);
+           };
+
+           auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>)
+           {
+             constexpr auto __inv_map = __inv_map_rank<_IndexType,
_Slices...>();
+             return array<_IndexType, _SubExtents::rank()>{
+               __stride(__inv_map[_Is], __slices...[__inv_map[_Is]])...};
+           };
+           return __impl(make_index_sequence<_SubExtents::rank()>());
+         }
+      };
+
+    template<typename _SubExtents, typename _Mapping, typename... _Slices>
+      constexpr auto
+      __substrides_standardized(const _Mapping& __mapping,
+                              const _Slices&... __slices)
+      {
+       using _IndexType = typename _Mapping::index_type;
+       using _Trait = _StridesTrait<__deduce_mapping_side<_Mapping>(),
+                                    _Mapping::extents_type::rank()>;
+       using _SubTrait = _StridesTrait<__deduce_mapping_side<_Mapping>(),
+                                       _SubExtents::rank()>;
+
+       constexpr size_t __sub_rank = _SubExtents::rank();
+
+       array<_IndexType, __sub_rank> __ret;
+       if constexpr (__sub_rank > 0)
+         {
+           constexpr auto __inv_map
+             = _Trait::template _S_inv_map<_IndexType, _Slices...>();
+           auto __loop = [&]<size_t... _Ks>(index_sequence<_Ks...>)
+           {
+             size_t __i0 = 0;
+             size_t __stride = 1;
+             auto __body = [&](size_t __k, auto __slice)
+             {
+               for (size_t __i = __i0; __i < __inv_map[__k]; ++__i)
+                 __stride *= _Trait::_S_extent(__mapping, __i);

I wonder if for all standardized mappings, we should not add _M_strides
(public) function to access that. This would be useful.

+
+               size_t __krev = _SubTrait::_S_idx(__k);
+               if constexpr (__is_strided_slice<decltype(__slice)>)
+                 __ret[__krev] = __stride * __slice.stride;
+               else
+                 __ret[__krev] = __stride;
+
+               __i0 = __inv_map[__k];
+             };
+
+             ((__body(_Ks,
__slices...[_Trait::_S_idx(__inv_map[_Ks])])),...);
+           };
+           __loop(make_index_sequence<__sub_rank>());
+         }
+       return __ret;
+      }
+
+
+    template<typename _SubExtents, typename _Mapping, typename... _Slices>
+      constexpr auto
+      __substrides(const _Mapping& __mapping, const _Slices&... __slices)
+      {
+       if constexpr (__deduce_mapping_side<_Mapping>() ==
_LayoutSide::__unknown)
+         return __substrides_generic<_SubExtents>(__mapping, __slices...);
+       else
+         return __substrides_standardized<_SubExtents>(__mapping,
__slices...);
+      }
+
+    template<typename _Slice, typename _IndexType>
+      concept __is_unit_stride_slice =


(__is_strided_slice<_Slice>
+         && __detail::__integral_constant_like<typename
_Slice::stride_type>

Usig is_constant_wrapper instead of  __integral_constant_like.

+         && _Slice::stride_type::value == 1)
+       || same_as<_Slice, full_extent_t>;
+
+    //                   _BlockSize - 1
+    // [full, ..., full, unit_slice    , ...]
+    template<typename _IndexType, size_t _BlockSize, typename... _Slices>

I think this funciton would read much better if we do normal template
parameter recursion,
i.e. have typename _Slice, typename... _RemSlices, we should never search a
block
with one element.

+      consteval bool
+      __is_block()
+      {
+       if constexpr (_BlockSize == 0 || _BlockSize > sizeof...(_Slices))
+         return false;
+       else if constexpr (_BlockSize == 1)
+         return __is_unit_stride_slice<_Slices...[0], _IndexType>;
+       else if constexpr (same_as<_Slices...[0], full_extent_t>)

This will be much simpler.

+         {
+           auto __recurse = []<size_t... _Is>(index_sequence<_Is...>)
+           {
+             return __is_block<_IndexType, _BlockSize - 1,
+                               _Slices...[_Is + 1]...>();
+           };
+           return __recurse(make_index_sequence<sizeof...(_Slices) -
1>());
+         }
+       else
+         return false;

+      }
+
+    //     __u              __u + _BlockSize - 1
+    // [*, full, ..., full,           unit_slice, *]
+    template<typename _IndexType, size_t _Start, size_t _BlockSize,
+            typename... _Slices>
+      consteval size_t
+      __find_block()

Hmm, I think that this function could be implemented much easier by having:
       bool full_map[sizeof...(Slices)]{ is_same_v<full_exent_t, Slices>....
};
Or even better having an array of classification of slices at the
begining of the functo
      constexpr SliceKind kinds[sizeof...(Slices)]{  function to classify,
full_extent -> full, strided -> stride, unit };
And then pass it to classification functions. That will go over what could
be returned.


+      {
+       static_assert(_BlockSize != dynamic_extent,
+         "The implementation can't handle submdspans with rank ==
size_t(-1)");
+
+       if constexpr (sizeof...(_Slices) == 0)
+         return dynamic_extent;
+       else if constexpr (__is_block<_IndexType, _BlockSize,
_Slices...>())
+         return _Start;
+       else
+         {
+           auto __recurse = []<size_t... _Is>(index_sequence<_Is...>)
+           {
+             return __find_block<_IndexType, _Start + 1, _BlockSize,
+                                 _Slices...[_Is + 1]...>();
+           };
+           return __recurse(make_index_sequence<sizeof...(_Slices) -
1>());
+         }
+      }
+
+    template<typename _IndexType, size_t _SubRank, typename... _Slices>
+      static consteval bool
+      __is_compact_block()
+      {
+       if constexpr (_SubRank == 0)
+         return false;
+       else
+         return  __find_block<_IndexType, 0, _SubRank, _Slices...>() == 0;
+      }
+
+    //                         __u
+    // [unit_slice, i, ..., k, full, ..., full, unit_slice, *]
+    template<typename _IndexType, size_t _SubRank, typename _Slice,
+            typename... _Slices>
+      static consteval size_t
+      __padded_block_begin_generic()
+      {
+       if constexpr (!__mdspan::__is_unit_stride_slice<_Slice,
_IndexType>)
+         return dynamic_extent;
+       else if constexpr (sizeof...(_Slices) == 0)
+         return dynamic_extent;
+       else
+         {
+           constexpr auto __u = __find_block<_IndexType, 0, _SubRank - 1,
+                                             _Slices...>();
+           if constexpr (__u != dynamic_extent)
+             return __u + 1;
+           else
+             return dynamic_extent;
+         }
+      }
+
+    template<_LayoutSide _Side, typename _IndexType, size_t _SubRank,
+             typename... _Slices>
+      static consteval size_t
+      __padded_block_begin()
+      {
+       if constexpr (_Side == _LayoutSide::__left)
+         return __padded_block_begin_generic<_IndexType, _SubRank,
+                                             _Slices...>();
+      }
+
+    template<_LayoutSide _Side>
+      struct _SubMdspanMapping;
+
+    template<>
+      struct _SubMdspanMapping<_LayoutSide::__left>
+      {
+       using _Layout = layout_left;
+       template<size_t _Pad> using _PaddedLayout =
layout_left_padded<_Pad>;
+
+       template<typename _Mapping, size_t _Us>
+         static consteval size_t
+         _S_pad()
+         {
+           using _Extents = typename _Mapping::extents_type;
+           constexpr auto __sta_exts = __static_extents<_Extents>(0, _Us);
+           if constexpr (!__all_static(__sta_exts))
+             return dynamic_extent;
+           else
+             return __fwd_prod(__sta_exts);
+         }
+
+       template<typename _IndexType, size_t _SubRank, typename... _Slices>
+         static consteval bool
+         _S_is_unpadded_submdspan()
+         { return __is_compact_block<_IndexType, _SubRank, _Slices...>();
}
+      };
+
+    template<typename _Mapping>
+      constexpr auto
+      __submdspan_mapping_impl(const _Mapping& __mapping)
+      { return submdspan_mapping_result{__mapping, 0}; }
+
+    template<typename _Mapping, typename... _Slices>
+      requires (sizeof...(_Slices) > 0)
+      constexpr auto
+      __submdspan_mapping_impl(const _Mapping& __mapping, _Slices...
__slices)
+      {
+       using _IndexType= typename _Mapping::index_type;
+       constexpr auto __side = __deduce_mapping_side<_Mapping>();
+       using _Trait = _SubMdspanMapping<__side>;
+
+       auto __offset = __suboffset(__mapping, __slices...);

The calls need to be qualified (and one above) and everywhere in general.
  +       auto __sub_exts = submdspan_extents(__mapping.
We should call  __mdspan::__subextents as we already canonicalized
slices.

+       using _SubExtents = decltype(__sub_exts);
+       constexpr auto __sub_rank = _SubExtents::rank();
+       if constexpr (_SubExtents::rank() == 0)
+         return submdspan_mapping_result{
+           typename _Trait::_Layout::mapping(__sub_exts), __offset};
+       else if constexpr (
+           _Trait::template _S_is_unpadded_submdspan<_IndexType,
__sub_rank,
+                                                     _Slices...>())
+         return submdspan_mapping_result{
+           typename _Trait::_Layout::mapping(__sub_exts), __offset};
+       else if constexpr (
+           constexpr auto __u = __padded_block_begin<__side, _IndexType,
+                                                    __sub_rank,
_Slices...>();
+           __u != dynamic_extent)
+         {
+           constexpr auto __pad = _Trait::template _S_pad<_Mapping,
__u>();
+           using _Layout = typename _Trait::template _PaddedLayout<__pad>;
+           return submdspan_mapping_result{
+             typename _Layout::mapping(__sub_exts, __mapping.stride(__u)),
+             __offset};
+         }
+       else
+         {
+           auto __sub_strides
+             = __substrides<_SubExtents>(__mapping, __slices...);
+           return submdspan_mapping_result{
+             layout_stride::mapping(__sub_exts, __sub_strides),
__offset};
+         }
+      }
  #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;
@@ -1168,20 +1532,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        template<typename _OExtents>
         constexpr explicit
         mapping(const _OExtents& __oexts, __mdspan::__internal_ctor)
noexcept
         : _M_extents(__oexts)
         {
           static_assert(__mdspan::__representable_size<_OExtents,
index_type>,
             "The size of OtherExtents must be representable as
index_type");

__glibcxx_assert(__mdspan::__is_representable_extents(_M_extents));
         }

+#if __glibcxx_submdspan
+      template<__mdspan::__valid_canonical_slice_type<index_type>...
_Slices>
+       requires (extents_type::rank() == sizeof...(_Slices))
+       friend constexpr auto
+       submdspan_mapping(const mapping& __mapping, _Slices... __slices)
+       { return __mdspan::__submdspan_mapping_impl(__mapping,
__slices...); }
+#endif // __glibcxx_submdspan
+
         [[no_unique_address]] extents_type _M_extents{};
      };

    namespace __mdspan
    {
      template<typename _Extents, typename... _Indices>
        constexpr typename _Extents::index_type
        __linear_index_right(const _Extents& __exts, _Indices... __indices)
        noexcept
        {
@@ -2824,16 +3196,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      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...);
      }
+
+  template<typename _ElementType, typename _Extents, typename _Layout,
+          typename _Accessor, typename... _Slices>
+    requires (sizeof...(_Slices) == _Extents::rank())
+    constexpr auto
+    submdspan(
+       const mdspan<_ElementType, _Extents, _Layout, _Accessor>& __md,
+       _Slices... __raw_slices)
+    {
+      auto [...__slices] = submdspan_canonicalize_slices(__md.extents(),
+                                                        __raw_slices...);

Again, we do not want to instantiate tuples, so _would do __impl lambda
trick here,
were we pass canonicalized slices as arguments.

+      auto __result = submdspan_mapping(__md.mapping(), __slices...);
+      return mdspan(__md.accessor().offset(__md.data_handle(),
__result.offset),
+         __result.mapping, typename
_Accessor::offset_policy(__md.accessor()));
+    }
  #endif // __glibcxx_submdspan

  _GLIBCXX_END_NAMESPACE_VERSION
  }
  #endif
  #endif
diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/
std.cc.in
index c2a9293b05a..2dac6a6d887 100644
--- a/libstdc++-v3/src/c++23/std.cc.in
+++ b/libstdc++-v3/src/c++23/std.cc.in
@@ -1878,22 +1878,22 @@ export namespace std
    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;
+  using std::submdspan;
  #endif
-  // 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/submdspan/submdspan.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan.cc
new file mode 100644
index 00000000000..53e91407a9c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan.cc
@@ -0,0 +1,369 @@
+// { dg-do run { target c++26 } }
+#include <mdspan>
+
+#include <iostream> // TODO remove
+#include <vector>
+#include <numeric>
+#include "../layout_traits.h"
+#include <testsuite_hooks.h>
+
+constexpr size_t dyn = std::dynamic_extent;
+constexpr auto all = std::full_extent;
+
+template<typename T>
+  constexpr bool is_strided_slice = false;
+
+template<typename O, typename E, typename S>
+  constexpr bool is_strided_slice<std::strided_slice<O, E, S>> = true;
+
+template<typename MDSpan>
+  constexpr void
+  fill(const MDSpan& md)
+  {
+    using IndexType = typename MDSpan::index_type;
+    auto exts = md.extents();
+    if constexpr (exts.rank() == 3)
+      for(IndexType i = 0; i < exts.extent(0); ++i)
+       for(IndexType j = 0; j < exts.extent(1); ++j)
+         for(IndexType k = 0; k < exts.extent(2); ++k)
+           md[i, j, k] = 100 * i + 10 * j + k;
+  }
+
+template<typename Int, size_t Rank>
+  class multi_index_generator
+  {
+    class EndIt
+    { };
+
+    class BeginIt
+    {
+    public:
+      constexpr
+      BeginIt(const std::array<Int, Rank>& shape)
+       : M_shape(shape)
+      { }
+
+      constexpr BeginIt&
+      operator++()
+      {
+       if constexpr (Rank > 0)
+         {
+           ++M_indices[Rank-1];
+           for(size_t i = Rank; i > 1; --i)
+             if (M_indices[i-1] == M_shape[i-1])
+               {
+                 M_indices[i-1] = 0;
+                 ++M_indices[i-2];
+               }
+         }
+       return *this;
+      }
+
+      constexpr auto
+      operator*()
+      { return M_indices; }
+
+      constexpr bool
+      operator==(EndIt)
+      {
+       if constexpr (Rank > 0)
+         return M_indices[0] == M_shape[0];
+       else
+         return true;
+      }
+
+    private:
+      std::array<Int, Rank> M_indices{};
+      std::array<Int, Rank> M_shape;
+    };
+
+  public:
+    constexpr
+    multi_index_generator(std::array<Int, Rank> shape)
+      : M_shape(shape)
+    { }
+
+    constexpr BeginIt
+    begin() const
+    { return BeginIt(M_shape); }
+
+    constexpr EndIt
+    end() const
+    { return EndIt{}; }
+
+  private:
+    std::array<Int, Rank> M_shape;
+  };
+
+constexpr bool
+test_multi_index()
+{
+  auto shape = std::array{3, 5, 7, 1};
+
+  std::vector<std::array<int, 4>> expected;
+  for (int i = 0; i < shape[0]; ++i)
+    for (int j = 0; j < shape[1]; ++j)
+      for (int k = 0; k <shape[2]; ++k)
+       for (int l = 0; l <shape[3]; ++l)
+         expected.push_back(std::array{i, j, k, l});
+
+  size_t i = 0;
+  for (auto actual : multi_index_generator(shape))
+    VERIFY(expected[i++] == actual);
+  return true;
+}
+
+static_assert(test_multi_index());
+
+struct
+collapse
+{ };
+
+template<typename... Slices>
+  consteval auto
+  inv_collapsed_index_map()
+  {
+    constexpr size_t rank = sizeof...(Slices);
+    auto is_collapsing = std::array{std::same_as<Slices, collapse>...};
+    constexpr auto collapsed_rank = ((!std::same_as<Slices, collapse>) +
... + 0);
+
+    std::array<size_t, collapsed_rank> ret;
+    if constexpr (collapsed_rank > 0)
+      for(size_t k = 0, i = 0; i < rank; ++i)
+       if (!is_collapsing[i])
+         ret[k++] = i;
+    return ret;
+  }
+
+static_assert(inv_collapsed_index_map<collapse, collapse, collapse>()
+             == std::array<size_t, 0>{});
+
+static_assert(inv_collapsed_index_map<collapse, decltype(all), collapse>()
+             == std::array<size_t, 1>{1});
+
+template<typename IndexType, typename Slice>
+  constexpr std::vector<IndexType>
+  make_selection(IndexType extent, const Slice& slice)
+  {
+    if constexpr (std::convertible_to<Slice, IndexType>)
+      return {static_cast<IndexType>(slice)};
+    else if constexpr (std::same_as<Slice, std::full_extent_t>)
+      {
+       auto ret = std::vector<IndexType>(static_cast<size_t>(extent));
+       std::ranges::iota(ret, 0);
+       return ret;
+      }
+    else if constexpr (is_strided_slice<Slice>)
+      {
+       auto ret = std::vector<IndexType>{};
+       size_t n = static_cast<size_t>(slice.extent);
+       for(size_t i = 0; i < n; i += slice.stride)
+         ret.push_back(slice.offset + i);
+       return ret;
+      }
+    else
+      {
+       auto [begin, end] = slice;
+       auto ret = std::vector<IndexType>(static_cast<size_t>(end -
begin));
+       std::ranges::iota(ret, begin);
+       return ret;
+      }
+  }
+
+template<typename Layout, size_t... I, typename... Slices>
+  constexpr bool
+  check_selection(std::index_sequence<I...>, auto md, Slices... slices)
+  {
+    auto exts = md.extents();
+    auto outer_shape = std::array{exts.extent(0), exts.extent(1),
exts.extent(2)};
+
+    constexpr auto full_index = inv_collapsed_index_map<Slices...>();
+    auto make_slice = [](size_t i, auto slice)
+    {
+      if constexpr (std::same_as<decltype(slice), collapse>)
+       return i;
+      else
+       return slice;
+    };
+
+    auto loop_body = [&]<size_t... J>(std::index_sequence<J...>, auto ijk,
+                                     auto... slices)
+    {
+      auto submd = submdspan(md, slices...[I]...);
+      auto selection = std::tuple{make_selection(exts.extent(I),
slices...[I])...};
+      auto inner_shape = std::array<size_t, full_index.size()>{
+       std::get<full_index[J]>(selection).size()...
+      };
+
+      for (auto ij : multi_index_generator(inner_shape))
+      {
+       ((ijk[full_index[J]] = get<full_index[J]>(selection)[ij[J]]),...);
+       VERIFY(submd[ij] == md[ijk]);
+      }
+    };
+
+    for (auto ijk : multi_index_generator(outer_shape))
+      loop_body(std::make_index_sequence<full_index.size()>(), ijk,
+               make_slice(ijk[I], slices...[I])...);
+    return true;
+  }
+
+template<typename Layout, typename...MD, typename... Slices>
+  constexpr bool
+  check_selection(std::mdspan<MD...> md, Slices... slices)
+  {
+    auto indices = std::make_index_sequence<sizeof...(slices)>();
+    return check_selection<Layout>(indices, md, slices...);
+  }
+
+template<typename Layout, typename IndexType, size_t... Extents,
+        typename... Slices>
+  constexpr bool
+  check_selection(std::extents<IndexType, Extents...>exts, Slices...
slices)
+  {
+    auto run = [&](auto m)
+    {
+      auto storage = std::vector<double>(m.required_span_size());
+      auto md = std::mdspan(storage.data(), m);
+      fill(md);
+      return check_selection<Layout>(md, slices...);
+    };
+
+    if constexpr (std::same_as<Layout, std::layout_stride>)
+      {
+       auto m = typename Layout::mapping(exts, std::array{15, 2, 50});
+       return run(m);
+      }
+    else
+      {
+       auto m = typename Layout::mapping(exts);
+       return run(m);
+      }
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_scalar_selection(auto exts)
+  {
+    check_selection<Layout>(exts, collapse{}, collapse{}, collapse{});
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_full_lines(auto exts)
+  {
+    check_selection<Layout>(exts, all, collapse{}, collapse{});
+    check_selection<Layout>(exts, collapse{}, all, collapse{});
+    check_selection<Layout>(exts, collapse{}, collapse{}, all);
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_full_blocks(auto exts)
+  {
+    check_selection<Layout>(exts, all, all, collapse{});
+    check_selection<Layout>(exts, all, collapse{}, all);
+    check_selection<Layout>(exts, collapse{}, all, all);
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_cubes(auto exts)
+  {
+    auto s0 = std::pair{0, 2};
+    auto s1 = std::pair{1, 4};
+    auto s2 = std::pair{3, 7};
+
+    check_selection<Layout>(exts, all, all, all);
+    check_selection<Layout>(exts, all, all, s2);
+    check_selection<Layout>(exts, s0, all, all);
+    check_selection<Layout>(exts, s0, all, s2);
+    check_selection<Layout>(exts, s0, s1, s2);
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_strided_line_selection(auto exts)
+  {
+    auto check = [&](auto s)
+    {
+      check_selection<Layout>(exts, collapse{}, s, collapse{});
+    };
+
+    check(std::strided_slice(0, 2, 2));
+    check(std::strided_slice(0, 3, 2));
+    check(std::strided_slice(1, 3, 2));
+    check(std::strided_slice(1, std::cw<3>, std::cw<2>));
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_strided_box_selection(auto exts)
+  {
+    auto s0 = std::strided_slice(0, 3, 2);
+    auto s1 = std::strided_slice(1, 4, 2);
+    auto s2 = std::strided_slice(0, 7, 3);
+
+    check_selection<Layout>(exts, s0, s1, s2);
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_all_cheap()
+  {
+    constexpr auto dyn_exts = std::extents(3, 5, 7);
+    constexpr auto sta_exts = std::extents<int, 3, 5, 7>{};
+
+    test_scalar_selection<Layout>(dyn_exts);
+    test_scalar_selection<Layout>(sta_exts);
+    static_assert(test_scalar_selection<Layout>(dyn_exts));
+    static_assert(test_scalar_selection<Layout>(sta_exts));
+
+    test_full_lines<Layout>(dyn_exts);
+    test_full_lines<Layout>(sta_exts);
+    static_assert(test_full_lines<Layout>(dyn_exts));
+    static_assert(test_full_lines<Layout>(sta_exts));
+
+    test_strided_box_selection<Layout>(dyn_exts);
+    test_strided_box_selection<Layout>(sta_exts);
+    static_assert(test_strided_box_selection<Layout>(dyn_exts));
+    static_assert(test_strided_box_selection<Layout>(sta_exts));
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_all_expensive()
+  {
+    auto run = [](auto exts)
+    {
+      test_full_blocks<Layout>(exts);
+      test_cubes<Layout>(exts);
+    };
+
+    run(std::extents(3, 5, 7));
+    run(std::extents<int, 3, 5, 7>{});
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_all()
+  {
+    test_all_cheap<Layout>();
+    test_all_expensive<Layout>();
+    return true;
+  }
+
+int
+main()
+{
+  test_all<std::layout_left>();
+  return 0;
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc
new file mode 100644
index 00000000000..a37d3cd588f
--- /dev/null
+++
b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc
@@ -0,0 +1,136 @@
+// { dg-do run { target c++26 } }
+#include <mdspan>
+
+#include <iostream> // TODO remove
+#include "../layout_traits.h"
+#include <testsuite_hooks.h>
+
+constexpr size_t dyn = std::dynamic_extent;
+
+template<typename Mapping, typename... Slices>
+  constexpr auto
+  call_submdspan_mapping(const Mapping& m, std::tuple<Slices...> slices)
+  {
+    auto impl = [&]<size_t... I>(std::index_sequence<I...>)
+    { return submdspan_mapping(m, get<I>(slices)...); };
+    return impl(std::make_index_sequence<sizeof...(Slices)>());
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_layout_unpadded_return_types()
+  {
+    constexpr auto padding_side =
DeducePaddingSide::from_typename<Layout>();
+    using Traits = LayoutTraits<padding_side>;
+
+    {
+      auto m0 = typename Layout::mapping(std::extents());
+      auto result = submdspan_mapping(m0);
+      using layout_type = typename decltype(result.mapping)::layout_type;
+      static_assert(std::same_as<layout_type, Layout>);
+    }
+
+    auto exts = Traits::make_extents(std::dims<5, int>(3, 5, 7, 11, 13));
+    auto m = typename Layout::mapping(exts);
+    auto all = std::full_extent;
+    auto s251 = std::strided_slice{2, 5, std::cw<1>};
+
+    {
+      auto slices = std::tuple{0, 0, 0, 0, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = typename decltype(result.mapping)::layout_type;
+      static_assert(std::same_as<layout_type, Layout>);
+    }
+
+    {
+      auto slices = std::tuple{all, all, all, s251, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = typename decltype(result.mapping)::layout_type;
+      static_assert(std::same_as<layout_type, Layout>);
+    }
+
+    {
+      auto s0 = std::strided_slice{1, 1, std::cw<1>};
+      auto slices = std::tuple{s0, all, all, s251, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = typename decltype(result.mapping)::layout_type;
+      static_assert(is_same_padded<padding_side, layout_type>);
+    }
+
+    {
+      auto s0 = std::strided_slice{1, 2, std::cw<1>};
+      auto slices = std::tuple{s0, all, all, s251, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = typename decltype(result.mapping)::layout_type;
+      static_assert(is_same_padded<padding_side, layout_type>);
+    }
+
+    {
+      auto s0 = std::strided_slice{1, 2, std::cw<1>};
+      auto slices = std::tuple{s0, 0, all, s251, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = typename decltype(result.mapping)::layout_type;
+      static_assert(is_same_padded<padding_side, layout_type>);
+    }
+
+    {
+      auto s0 = std::strided_slice{1, 2, 1};
+      auto slices = std::tuple{s0, all, all, s251, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = decltype(result.mapping)::layout_type;
+      static_assert(std::same_as<layout_type, std::layout_stride>);
+    }
+
+    {
+      auto slices = std::tuple{1, all, all, s251, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = decltype(result.mapping)::layout_type;
+      static_assert(std::same_as<layout_type, std::layout_stride>);
+    }
+
+    {
+      auto s3 = std::strided_slice{2, std::cw<7>, std::cw<2>};
+      auto slices = std::tuple{all, all, all, s3, 0};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      using layout_type = decltype(result.mapping)::layout_type;
+      static_assert(std::same_as<layout_type, std::layout_stride>);
+    }
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_layout_unpadded_padding_value()
+  {
+    using Traits =
LayoutTraits<DeducePaddingSide::from_typename<Layout>()>;
+    auto s0 = std::strided_slice{size_t(1), size_t(2),
std::cw<size_t(1)>};
+    auto s3 = std::strided_slice{size_t(2), size_t(5),
std::cw<size_t(1)>};
+    auto all = std::full_extent;
+
+    auto check = [&](auto exts, size_t expected)
+    {
+      auto m = typename Layout::mapping(Traits::make_extents(exts));
+      auto slices = std::tuple{s0, size_t(0), all, s3, size_t(0)};
+      auto result = call_submdspan_mapping(m, Traits::make_tuple(slices));
+      auto padding_value = decltype(result.mapping)::padding_value;
+      VERIFY(padding_value == expected);
+    };
+
+    check(std::extents(std::cw<3>, std::cw<5>, std::cw<7>, 11, 13), 3*5);
+    check(std::extents(std::cw<3>, std::cw<5>, 7, 11, 13), 3*5);
+    check(std::extents(std::cw<3>, 5, 7, 11, 13), dyn);
+    check(std::extents(3, 5, 7, 11, 13), dyn);
+    return true;
+  }
+
+int
+main()
+{
+  test_layout_unpadded_return_types<std::layout_left>();
+  static_assert(test_layout_unpadded_return_types<std::layout_left>());
+
+  test_layout_unpadded_padding_value<std::layout_left>();
+  static_assert(test_layout_unpadded_padding_value<std::layout_left>());
+  return 0;
+}
+
diff --git
a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc
new file mode 100644
index 00000000000..4f9aad81cb7
--- /dev/null
+++
b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc
@@ -0,0 +1,102 @@
+// { dg-do compile { target c++26 } }
+#include <mdspan>
+
+#include <vector>
+
+template<typename Layout, typename... Slices>
+  constexpr bool
+  check_slice_range(Slices... slices)
+  {
+    auto m = typename Layout::mapping<std::extents<int, 3, 5, 7>>{};
+    auto storage = std::vector<double>(m.required_span_size());
+    auto md = std::mdspan(storage.data(), m);
+
+    auto submd = submdspan(md, slices...);           // { dg-error
"expansion of" }
+    (void) submd;
+    return true;
+  }
+
+template<typename Layout>
+  constexpr bool
+  test_int_under()
+  {
+    check_slice_range<Layout>(1, -1, 2);             // { dg-error
"expansion of" }
+    return true;
+  }
+static_assert(test_int_under<std::layout_left>());   // { dg-error
"expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_int_over()
+  {
+    check_slice_range<Layout>(1, 5, 2);              // { dg-error
"expansion of" }
+    return true;
+  }
+static_assert(test_int_over<std::layout_left>());    // { dg-error
"expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_tuple_under()
+  {
+    check_slice_range<Layout>(1, std::tuple{-1, 2}, 2);  // { dg-error
"expansion of" }
+    return true;
+  }
+static_assert(test_tuple_under<std::layout_left>());     // { dg-error
"expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_tuple_reversed()
+  {
+    check_slice_range<Layout>(1, std::tuple{3, 2}, 2);   // { dg-error
"expansion of" }
+    return true;
+  }
+static_assert(test_tuple_reversed<std::layout_left>());   // { dg-error
"expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_tuple_over()
+  {
+    check_slice_range<Layout>(1, std::tuple{0, 6}, 2); // { dg-error
"expansion of" }
+    return true;
+  }
+static_assert(test_tuple_over<std::layout_left>());   // { dg-error
"expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_strided_slice_zero()
+  {
+    check_slice_range<Layout>(1, std::strided_slice{1, 1, 0}, 2);  // {
dg-error "expansion of" }
+    return true;
+  }
+static_assert(test_strided_slice_zero<std::layout_left>());   // {
dg-error "expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_strided_slice_offset_under()
+  {
+    check_slice_range<Layout>(1, std::strided_slice{-1, 1, 1}, 2);   // {
dg-error "expansion of" }
+    return true;
+  }
+static_assert(test_strided_slice_offset_under<std::layout_left>());   //
{ dg-error "expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_strided_slice_offset_over()
+  {
+    check_slice_range<Layout>(1, std::strided_slice{6, 0, 1}, 2);    // {
dg-error "expansion of" }
+    return true;
+  }
+static_assert(test_strided_slice_offset_over<std::layout_left>());   // {
dg-error "expansion of" }
+
+template<typename Layout>
+  constexpr bool
+  test_strided_slice_extent_over()
+  {
+    check_slice_range<Layout>(1, std::strided_slice{1, 5, 1}, 2);    // {
dg-error "expansion of" }
+    return true;
+  }
+static_assert(test_strided_slice_extent_over<std::layout_left>());   // {
dg-error "expansion of" }
+
+// { 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