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.


> ---
>  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