On Sat, Nov 15, 2025 at 12:52 PM Luc Grosheintz <[email protected]> wrote:
> > > On 11/14/25 15:09, Luc Grosheintz 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]> > > --- > > libstdc++-v3/include/std/mdspan | 420 +++++++++++++++++- > > 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, 1004 insertions(+), 25 deletions(-) > > 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 1d506f0338e..1f6cbfd0967 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> > > + constexpr _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) > > @@ -1003,20 +1013,383 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > __assert_valid_slices(const _Extents& __exts, const _Slices&... > __slices) > > { > > constexpr auto __rank = _Extents::rank(); > > if constexpr (__rank > 0) > > { > > constexpr auto [...__k] = _IotaArray<__rank>; > > This is not supported by Clang: > > <source>:6:5: error: decomposition declaration cannot be declared > 'constexpr' > 6 | constexpr auto [...i] = std::_IotaArray<5>; > | ^~~~~~~~~ > https://godbolt.org/z/z5oqczahb > > Do we use it anyway? (There's numerous other opportunities to > use this pattern, I wanted to ask first before making the patch > consistent.) > When structured binding decomposition is used, the FTM (and thus whole feature), should have additional condition of __cpp_structured_bindings >= 202411L, and thus be unavailable when used with clang. I believe such decision should be taken at the beginning of development, and have strong justification. I think we should make submdspan available with mdspan, and thus do not use structured binding in submdspan, at least yet. > > > ((__assert_valid_slice(__extract_extent<__k>(__exts), > > __slices...[__k])),...); > > } > > } > > + > > + template<typename _IndexType, typename _Slice> > > + constexpr _IndexType > > + __slice_begin(_Slice __slice) > > + { > > + 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 (is_convertible_v<_Slice, _IndexType>) > > + 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); > > + }; > > + > > + 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); > > + > > + 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)...); > > + } > > + > > + 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; > > + } > > + > > + enum class _LayoutSide > > + { > > + __left, > > + __right, > > + __unknown > > + }; > > + > > + template<typename _Mapping> > > + constexpr _LayoutSide > > + __deduce_mapping_side() > > + { > > + if constexpr (__is_left_padded_mapping<_Mapping> > > + || __mapping_of<layout_left, _Mapping>) > > + return _LayoutSide::__left; > > + if constexpr (__is_left_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 _Extents> > > + static constexpr typename _Extents::index_type > > + _S_extent(const _Extents& __exts, size_t __k) > > + { return __exts.extent(_S_idx(__k)); } > > + > > + template<typename _IndexType, typename... _Slices> > > + static consteval auto > > + _S_inv_map() > > + { > > + static_assert(_Side != _LayoutSide::__unknown); > > + constexpr auto [...__i] = _IotaArray<_Rank>; > > + return __inv_map_rank<_IndexType, > _Slices...[_S_idx(__i)]...>(); > > + } > > + }; > > + > > + 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); > > + }; > > + > > + constexpr auto [...__i] = _IotaArray<_SubExtents::rank()>; > > + constexpr auto __inv_map = __inv_map_rank<_IndexType, > _Slices...>(); > > + return array<_IndexType, _SubExtents::rank()>{ > > + __stride(__inv_map[__i], __slices...[__inv_map[__i]])...}; > > + } > > + }; > > + > > + template<typename _SubExtents, typename _Mapping, typename... > _Slices> > > + constexpr auto > > + __substrides_standarized(const _Mapping& __mapping, > > + const _Slices&... __slices) > > + { > > The motivation is that __substrides_generic is quadratic in rank (it > computes stride(k) rank-times, and stride(k) is (in general) k > multiplications. > > > + 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.extents(), __i); > > + > > + 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_standarized<_SubExtents>(__mapping, > __slices...); > > + else > > + return __substrides_generic<_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> > > + && _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> > > + 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>) > > + { > > + 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() > > + { > > + 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() > > + { > > + 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> > > + struct _SubMdspanMapping; > > + > > + template<> > > + struct _SubMdspanMapping<_LayoutSide::__left> > > + { > > + using _Layout = layout_left; > > + template<size_t _Pad> using _PaddedLayout = > layout_left_padded<_Pad>; > > + > > + template<typename _Extents, size_t _Us> > > + static constexpr size_t > > + _S_pad() > > + { > > + 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_compact_block() > > + { return __is_compact_block<_IndexType, _SubRank, _Slices...>(); > } > > + > > + template<typename _IndexType, size_t _SubRank, typename... _Slices> > > + static consteval size_t > > + _S_padded_block_begin() > > + { return __padded_block_begin<_IndexType, _SubRank, > _Slices...>(); } > > + }; > > + > > All of __submdspan_mapping_impl is underindented. > > > + 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 _Extents = typename _Mapping::extents_type; > > + using _IndexType= typename _Mapping::index_type; > > + using _Trait = > _SubMdspanMapping<__deduce_mapping_side<_Mapping>()>; > > + > > + auto __offset = __suboffset(__mapping, __slices...); > > + auto __sub_exts = submdspan_extents(__mapping.extents(), > __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_compact_block<_IndexType, > > + > __sub_rank, > > + > _Slices...>()) > > + return submdspan_mapping_result{ > > + typename _Trait::_Layout::mapping(__sub_exts), __offset}; > > + else if constexpr ( > > + constexpr auto __u > > + = _Trait::template _S_padded_block_begin<_IndexType, > __sub_rank, > > + _Slices...>(); > > + __u != dynamic_extent) > > + { > > + constexpr auto __pad = _Trait::template _S_pad<_Extents, > __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; > > @@ -1144,20 +1517,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 > > { > > @@ -2739,44 +3120,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > 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, 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; > > - } > > - > > 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...>) > > { > > @@ -2824,16 +3181,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...); > > + 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 2dfdf13f0d1..6306aad0498 100644 > > --- a/libstdc++-v3/src/c++23/std.cc.in > > +++ b/libstdc++-v3/src/c++23/std.cc.in > > @@ -1876,22 +1876,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" } > >
