[PATCH v4 1/5] libstdc++: Implement submdspan and submdspan_mapping for layout_left. [PR110352]
Luc Grosheintz
luc.grosheintz@gmail.com
Fri Dec 5 15:28:36 GMT 2025
On 12/5/25 11:04, Tomasz Kaminski wrote:
> On Thu, Dec 4, 2025 at 10:34 PM Luc Grosheintz <luc.grosheintz@gmail.com>
> 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 <luc.grosheintz@gmail.com>
>>
> This really looks solid to me now, thank you for the update. I will go over
> the remaining ones in the afternoon.
> Comments below:
> - slice-mapping is not SFINEAing correctly (body inside lambda inside
> concept is not checked),
> left some suggestions
> - we can eliminate few <size_t Size> parameters, and make it normal
> function parameters now
> - some comment about extracting span declaration
> - submdspan.cc test needs to be parallelized, already is timeint out, left
> some suggestion bellow,
> this apply to remaining patches also
>
>> ---
>> libstdc++-v3/include/std/mdspan | 557 ++++++++++++++++--
>> 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 | 161 +++++
>> 5 files changed, 1163 insertions(+), 62 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 bfec288aded..54739c0008d 100644
>> --- a/libstdc++-v3/include/std/mdspan
>> +++ b/libstdc++-v3/include/std/mdspan
>> @@ -357,40 +357,50 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> static_assert(__is_standard_integer<_ExtentType>::value
>> || __detail::__integral_constant_like<_ExtentType>);
>> static_assert(__is_standard_integer<_StrideType>::value
>> || __detail::__integral_constant_like<_StrideType>);
>>
>> using offset_type = _OffsetType;
>> using extent_type = _ExtentType;
>> using stride_type = _StrideType;
>>
>> [[no_unique_address]] offset_type offset{};
>> [[no_unique_address]] extent_type extent{};
>> [[no_unique_address]] stride_type stride{};
>> };
>>
>> template<typename _Mapping>
>> struct submdspan_mapping_result
>> {
>> [[no_unique_address]] _Mapping mapping = _Mapping();
>> size_t offset{};
>> };
>> +
>> + template<typename _Tp>
>> + constexpr bool __is_submdspan_mapping_result = false;
>> +
>> + template<typename _Mapping>
>> + constexpr bool
>> __is_submdspan_mapping_result<submdspan_mapping_result<_Mapping>> = true;
>> +
>> + template<typename _Mapping>
>> + concept __submdspan_mapping_result =
>> __is_submdspan_mapping_result<_Mapping>;
>> +
>> #endif // __glibcxx_submdspan
>>
>> template<typename _IndexType, size_t... _Extents>
>> class extents
>> {
>> static_assert(__is_standard_integer<_IndexType>::value,
>> "IndexType must be a signed or unsigned integer type");
>> static_assert(
>> (__mdspan::__valid_static_extent<_Extents, _IndexType> && ...),
>> "Extents must either be dynamic or representable as IndexType");
>>
>> using _Storage = __mdspan::_ExtentsStorage<
>> _IndexType, array<size_t, sizeof...(_Extents)>{_Extents...}>;
>> [[no_unique_address]] _Storage _M_exts;
>>
>> public:
>> using index_type = _IndexType;
>> using size_type = make_unsigned_t<index_type>;
>> using rank_type = size_t;
>>
>> @@ -572,40 +582,50 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>
>> template<typename _Extents>
>> constexpr typename _Extents::index_type
>> __fwd_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)
>> 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)
>> return __r == 0 ? __exts.extent(1) : 1;
>> else if constexpr
>> (__all_dynamic(std::span(__sta_exts).last(__rank-1)))
>> return __extents_prod(__exts, 1, __r + 1, __rank);
>> else
>> {
>> size_t __sta_prod = __rev_partial_prods<__sta_exts>[__r];
>> return __extents_prod(__exts, __sta_prod, __r + 1, __rank);
>> }
>> }
>>
>> @@ -849,64 +869,503 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> return std::cmp_less_equal(__min, __value)
>> && std::cmp_less_equal(__value, __max);
>> }
>>
>> template<typename _Tp>
>> constexpr bool __is_constant_wrapper = false;
>>
>> template<_CwFixedValue _Xv, typename _Tp>
>> constexpr bool __is_constant_wrapper<constant_wrapper<_Xv, _Tp>>
>> = true;
>>
>> template<size_t _Index, typename _Extents>
>> constexpr auto
>> __extract_extent(const _Extents& __exts)
>> {
>> using _IndexType = typename _Extents::index_type;
>> return extents<_IndexType, _Extents::static_extent(_Index)>{
>> __exts.extent(_Index)};
>> }
>>
>> + template<typename _Slice, typename _IndexType>
>> + concept __acceptable_slice_type = same_as<_Slice, full_extent_t>
>> + || same_as<_Slice, _IndexType> || __is_constant_wrapper<_Slice>
>> + || __is_strided_slice<_Slice>;
>> +
>> template<typename _IndexType, typename... _Slices>
>> consteval auto
>> __subrank()
>> {
>> return (static_cast<size_t>(!convertible_to<_Slices, _IndexType>)
>> + ... + 0);
>> }
>>
>> template<typename _IndexType, typename... _Slices>
>> consteval auto
>> __inv_map_rank()
>> {
>> constexpr auto __rank = sizeof...(_Slices);
>> constexpr auto __sub_rank = __subrank<_IndexType, _Slices...>();
>> auto __map = std::array<size_t, __sub_rank>{};
>> auto __is_int_like = std::array<bool, __rank>{
>> convertible_to<_Slices, _IndexType>...};
>>
>> size_t __i = 0;
>> for (size_t __k = 0; __k < __rank; ++__k)
>> if (!__is_int_like[__k])
>> __map[__i++] = __k;
>> return __map;
>> }
>> +
>> + template<typename _Slice>
>> + constexpr auto
>> + __slice_begin(_Slice __slice)
>> + {
>> + if constexpr (same_as<_Slice, full_extent_t>)
>> + return 0;
>> + else if constexpr (__is_strided_slice<_Slice>)
>> + return __slice.offset;
>> + else
>> + return __slice; // collapsing slice
>> + }
>> +
>> + 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 constexpr (same_as<_Slice, full_extent_t>
>> + && __ext.static_extent(0) != 0
>> + && __ext.static_extent(0) != dynamic_extent)
>> + return false;
>> + else
>> + return __mdspan::__slice_begin(__slice) == __ext.extent(0);
>> + };
>> +
>> + const auto& __exts = __mapping.extents();
>> + return ((__is_past_the_end(__slices...[_Is],
>> +
>> __mdspan::__extract_extent<_Is>(__exts))) || ...);
>> + };
>> +
>> + if constexpr ((same_as<_Slices, full_extent_t> && ...))
>> + return __mdspan::__offset(__mapping);
>> +
>>
> + if
>> (__any_past_the_end(std::make_index_sequence<sizeof...(__slices)>()))
>> + return __mapping.required_span_size();
>>
> + return __mapping(__mdspan::__slice_begin(__slices)...);
>> + }
>> +
>> + template<typename _IndexType, size_t _Extent, typename _Slice>
>> + consteval size_t
>> + __static_slice_extent()
>> + {
>> + if constexpr (same_as<_Slice, full_extent_t>)
>> + return _Extent;
>> + else if constexpr (same_as<_Slice,
>> constant_wrapper<_IndexType(0)>>)
>> + return 0;
>> + else if constexpr (__is_constant_wrapper<typename
>> _Slice::extent_type>
>> + && __is_constant_wrapper<typename
>> _Slice::stride_type>)
>> + return 1 + ((typename _Slice::extent_type{}) - 1)
>> + / (typename _Slice::stride_type{});
>> + else
>> + return dynamic_extent;
>> + }
>> +
>> + template<size_t _K, typename _Extents, typename _Slice>
>> + constexpr typename _Extents::index_type
>> + __dynamic_slice_extent(const _Extents& __exts, _Slice __slice)
>> + {
>> + if constexpr (__is_strided_slice<_Slice>)
>> + return __slice.extent == 0 ? 0 : 1 + (__slice.extent - 1) /
>> __slice.stride;
>> + else
>> + return __exts.extent(_K);
>> + }
>> +
>> + template<typename _IndexType, size_t... _Extents, typename... _Slices>
>> + requires (sizeof...(_Slices) == sizeof...(_Extents))
>> + constexpr auto
>> + __subextents(const extents<_IndexType, _Extents...>& __exts,
>> + _Slices... __slices)
>> + {
>> + constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType,
>> _Slices...>();
>> + auto __impl = [&]<size_t...
>> _Indices>(std::index_sequence<_Indices...>)
>> + {
>> + using _SubExts = extents<_IndexType,
>> + __mdspan::__static_slice_extent<_IndexType,
>> + _Extents...[__inv_map[_Indices]],
>> + _Slices...[__inv_map[_Indices]]>()...>;
>> + if constexpr (_SubExts::rank_dynamic() == 0)
>> + return _SubExts{};
>> + else
>> + {
>> + using _StaticSubExtents = __mdspan::_StaticExtents<
>> + __mdspan::__static_extents<_SubExts>()>;
>> + auto __create = [&]<size_t...
>> _Is>(std::index_sequence<_Is...>)
>> + {
>> + constexpr auto __slice_idx = [__inv_map](size_t __i)
>> consteval
>> + {
>> + return
>> __inv_map[_StaticSubExtents::_S_dynamic_index_inv(__i)];
>> + };
>> +
>> + return
>> _SubExts{__mdspan::__dynamic_slice_extent<__slice_idx(_Is)>(
>> + __exts, __slices...[__slice_idx(_Is)])...};
>> + };
>> + constexpr auto __dyn_subrank = _SubExts::rank_dynamic();
>> + return __create(std::make_index_sequence<__dyn_subrank>());
>> + }
>> + };
>> +
>> + return __impl(std::make_index_sequence<__inv_map.size()>());
>> + }
>> +
>> + enum class _LayoutSide
>> + {
>> + __left,
>> + __right,
>> + __unknown
>> + };
>> +
>> + template<typename _Mapping>
>> + consteval _LayoutSide
>> + __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;
>> + }
>> +
>> + // Unifies the formulas for computing strides for padded and
>> unpadded
>> + // layouts.
>> + template<typename _Mapping>
>> + static constexpr typename _Mapping::index_type
>> + _S_padded_extent(const _Mapping& __mapping, size_t __k)
>> + {
>> + if (__k == 0)
>> + return __mapping.stride(_S_idx(1));
>> + 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>(std::index_sequence<_Is...>)
>> + {
>> + return __mdspan::__inv_map_rank<_IndexType,
>> _Slices...[_S_idx(_Is)]...>();
>> + };
>> + return __impl(std::make_index_sequence<_Rank>());
>> + }
>> + };
>> +
>> + template<typename _SubExts, typename _Mapping, typename... _Slices>
>> + constexpr auto
>> + __substrides_generic(const _Mapping& __mapping, const _Slices&...
>> __slices)
>> + {
>> + using _IndexType = typename _Mapping::index_type;
>> + if constexpr (_SubExts::rank() == 0)
>> + return array<_IndexType, _SubExts::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>(std::index_sequence<_Is...>)
>> + {
>> + constexpr auto __inv_map
>> + = __mdspan::__inv_map_rank<_IndexType, _Slices...>();
>> + return array<_IndexType, _SubExts::rank()>{
>> + __stride(__inv_map[_Is], __slices...[__inv_map[_Is]])...};
>> + };
>> + return __impl(std::make_index_sequence<_SubExts::rank()>());
>> + }
>> + };
>> +
>> + template<typename _SubExts, typename _Mapping, typename... _Slices>
>> + constexpr auto
>> + __substrides_standardized(const _Mapping& __mapping,
>> + const _Slices&... __slices)
>> + {
>> + using _IndexType = typename _Mapping::index_type;
>> + using _Trait = _StridesTrait<__mapping_side<_Mapping>(),
>> + _Mapping::extents_type::rank()>;
>> + using _SubTrait = _StridesTrait<__mapping_side<_Mapping>(),
>> _SubExts::rank()>;
>> +
>> + constexpr size_t __sub_rank = _SubExts::rank();
>> +
>> + std::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>(std::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_padded_extent(__mapping, __i);
>> +
>> + size_t __krev = _SubTrait::_S_idx(__k);
>> + if constexpr (__is_strided_slice<decltype(__slice)>)
>> + __ret[__krev] = __stride * __slice.stride;
>>
> We seem to be missing the check that __slice.stride < __slice.extent.
>
>> + else
>> + __ret[__krev] = __stride;
>> +
>> + __i0 = __inv_map[__k];
>> + };
>> +
>> + ((__body(_Ks,
>> __slices...[_Trait::_S_idx(__inv_map[_Ks])])),...);
>> + };
>> + __loop(std::make_index_sequence<__sub_rank>());
>> + }
>> + return __ret;
>> + }
>> +
>> +
>> + template<typename _SubExts, typename _Mapping, typename... _Slices>
>> + constexpr auto
>> + __substrides(const _Mapping& __mapping, const _Slices&... __slices)
>> + {
>> + if constexpr (__mdspan::__mapping_side<_Mapping>() ==
>> _LayoutSide::__unknown)
>> + return __mdspan::__substrides_generic<_SubExts>(__mapping,
>> __slices...);
>> + else
>> + return __mdspan::__substrides_standardized<_SubExts>(__mapping,
>> __slices...);
>> + }
>> +
>> + template<typename _Slice>
>> + concept __is_unit_stride_slice =
>> (__mdspan::__is_strided_slice<_Slice>
>> + && __mdspan::__is_constant_wrapper<typename _Slice::stride_type>
>> + && _Slice::stride_type::value == 1)
>> + || std::same_as<_Slice, full_extent_t>;
>> +
>> + // These are (forced) exclusive categories:
>> + // - full & collapsing: obvious,
>> + // - unit_strided_slice: strided_slice{a, b, cw<1>}, but not `full`,
>> + // - strided_slice: strided_slice{a, b, c} with c != cw<1>.
>> + enum class _SliceKind
>> + {
>> + __strided_slice,
>> + __unit_strided_slice,
>> + __full,
>> + __collapsing
>> + };
>> +
>> + template<typename _Slice>
>> + consteval _SliceKind
>> + __make_slice_kind()
>> + {
>> + if constexpr (std::same_as<_Slice, full_extent_t>)
>> + return _SliceKind::__full;
>> + else if constexpr (__mdspan::__is_strided_slice<_Slice>)
>> + {
>> + if constexpr (__mdspan::__is_unit_stride_slice<_Slice>)
>> + return _SliceKind::__unit_strided_slice;
>> + else
>> + return _SliceKind::__strided_slice;
>> + }
>> + else
>> + return _SliceKind::__collapsing;
>> + }
>> +
>> + template<typename... _Slices>
>> + consteval array<_SliceKind, sizeof...(_Slices)>
>> + __make_slice_kind_array()
>> + {
>> + return array<_SliceKind, sizeof...(_Slices)>{
>> + __mdspan::__make_slice_kind<_Slices>()...};
>> + }
>> +
>> + // _BlockSize - 1
>> + // [full, ..., full, unit_slice , *]
>> + template<size_t _BlockSize>
>>
> Make this a normal function parameter, i.e. size_t _BlockSioe.
>
>> + consteval bool
>> + __is_block(span<const _SliceKind> __slice_kinds)
>> + {
>> + if (_BlockSize == 0)
>> + return false;
>> +
>> + if (_BlockSize > __slice_kinds.size())
>> + return false;
>> +
>> + for (size_t __i = 0; __i < _BlockSize - 1; ++__i)
>> + if (__slice_kinds[__i] != _SliceKind::__full)
>> + return false;
>> +
>> + auto __last = __slice_kinds[_BlockSize - 1];
>> + return __last == _SliceKind::__full
>> + || __last == _SliceKind::__unit_strided_slice;
>> + }
>> +
>> + // __u __u + _SubRank-2
>> + // [unit_slice, i, ..., k, full, ..., full, unit_slice, *]
>> + template<size_t _SubRank>
>>
> // Same here for _SubRank
>
>> + static consteval size_t
>> + __padded_block_begin_generic(span<const _SliceKind> __slice_kinds)
>> + {
>> + if (__slice_kinds[0] != _SliceKind::__full
>> + && __slice_kinds[0] != _SliceKind::__unit_strided_slice)
>> + return dynamic_extent;
>> + else if (__slice_kinds.size() == 1)
>> + return dynamic_extent;
>> + else
>> + {
>> + static_assert(_SubRank != dynamic_extent,
>> + "The implementation can't handle submdspans with rank ==
>> size_t(-1)");
>> +
>> + size_t __u = 1;
>> + while(__u < __slice_kinds.size()
>> + && __slice_kinds[__u] == _SliceKind::__collapsing)
>> + ++__u;
>> +
>> + if
>> (__mdspan::__is_block<_SubRank-1>(__slice_kinds.subspan(__u)))
>> + return __u;
>> + return dynamic_extent;
>>
> That looks really nice.
>
>> + }
>> + }
>> +
>> + template<_LayoutSide _Side, size_t _SubRank, size_t _Nm>
>>
> Again, make _SubRank a template parameter.
>
>> + static consteval size_t
>> + __padded_block_begin(span<const _SliceKind, _Nm> __slice_kinds)
>> + {
>> + if constexpr (_Side == _LayoutSide::__left)
>> + return
>> __mdspan::__padded_block_begin_generic<_SubRank>(__slice_kinds);
>> + else
>> + {
>> + std::array<_SliceKind, _Nm> __rev_slice_kinds;
>>
> I would name it __rev_slice_array
>
>> + for(size_t __i = 0; __i < _Nm; ++__i)
>> + __rev_slice_kinds[__i] = __slice_kinds[_Nm - 1 - __i];
>>
> And declare below.
> std::span<const _SliceKind, _Nm>
> __rev_slick_kinds(__rev_slice_array);
>
>> +
>> + auto __u = __mdspan::__padded_block_begin_generic<_SubRank>(
>> + std::span<const _SliceKind>(__rev_slice_kinds));
>> + return __u == dynamic_extent ? dynamic_extent : _Nm - 1 - __u;
>> + }
>> + }
>> +
>> + 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 =
>> __mdspan::__static_extents<_Extents>(0, _Us);
>> + if constexpr (!__mdspan::__all_static(__sta_exts))
>> + return dynamic_extent;
>> + else
>> + return __mdspan::__fwd_prod(__sta_exts);
>> + }
>> +
>> + template<size_t _SubRank, size_t _Nm>
>> + static consteval bool
>> + _S_is_unpadded_submdspan(span<const _SliceKind, _Nm>
>> __slice_kinds)
>> + { return __mdspan::__is_block<_SubRank>(__slice_kinds); }
>> + };
>> +
>> + 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)
>> + {
>> + constexpr auto __side = __mdspan::__mapping_side<_Mapping>();
>> + using _Trait = _SubMdspanMapping<__side>;
>> +
>> + constexpr auto __slice_kinds =
>> __mdspan::__make_slice_kind_array<_Slices...>();
>>
> Again here, declaring __slice_kinds_array and __slice_kind as span below
> it, we are creating
> the span 3 times below.
>
I might be doing it wrong, but the compiler is adamant that this isn't
permitted. Essentially, it says that __slice_kinds_span isn't a constant
expression. Clang expands: it's because the pointer in __slice_kinds_array
can be different on different invocations.
Godbolt: https://godbolt.org/z/vPPhPxva6
Easy fix: pass the array by value (just this once). We anyway need to know
the size, passing as an array, ensures that the size isn't `size_t(-1)`,
accidentally. Since we never need subspans of the array (at this point)
it should be fine. (There's no copying because it's always passed to
consteval functions).
>> + auto __offset = __mdspan::__suboffset(__mapping, __slices...);
>> + auto __sub_exts = __mdspan::__subextents(__mapping.extents(),
>> __slices...);
>> + using _SubExts = decltype(__sub_exts);
>> + constexpr auto __sub_rank = _SubExts::rank();
>> + if constexpr (__sub_rank == 0)
>> + return submdspan_mapping_result{
>> + typename _Trait::_Layout::mapping(__sub_exts), __offset};
>> + else if constexpr (
>> + _Trait::template _S_is_unpadded_submdspan<__sub_rank>(
>> + std::span<const _SliceKind,
>> __slice_kinds.size()>(__slice_kinds)))
>> + return submdspan_mapping_result{
>> + typename _Trait::_Layout::mapping(__sub_exts), __offset};
>> + else if constexpr (
>> + constexpr auto __u = __padded_block_begin<__side, __sub_rank>(
>> + span<const _SliceKind, __slice_kinds.size()>(__slice_kinds));
>> + __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
>> + = __mdspan::__substrides<_SubExts>(__mapping, __slices...);
>> + return submdspan_mapping_result{
>> + layout_stride::mapping(__sub_exts, __sub_strides),
>> __offset};
>> + }
>> + }
>> +
>> + // Enables ADL-only calls from submdspan.
>> + void submdspan_mapping() = delete;
>>
> Please put it before the concept __sliceable_mapping definition.
>
>> #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;
>> using rank_type = typename extents_type::rank_type;
>> using layout_type = layout_left;
>>
>> static_assert(__mdspan::__representable_size<extents_type,
>> index_type>,
>> "The size of extents_type must be representable as index_type");
>>
>> constexpr
>> mapping() noexcept = default;
>>
>> constexpr
>> @@ -1014,40 +1473,48 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> }
>>
>> template<typename _OExtents>
>> requires (extents_type::rank() == _OExtents::rank())
>> friend constexpr bool
>> operator==(const mapping& __self, const mapping<_OExtents>&
>> __other)
>> noexcept
>> { return __self.extents() == __other.extents(); }
>>
>> private:
>> 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::__acceptable_slice_type<index_type>... _Slices>
>>
> Make this a static assert inside submdspan_impl, we do not need a concept
> check here.
>
>> + 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
>> {
>> using _IndexType = typename _Extents::index_type;
>> array<_IndexType, sizeof...(__indices)> __ind_arr{__indices...};
>> _IndexType __res = 0;
>> if constexpr (sizeof...(__indices) > 0)
>> {
>> _IndexType __mult = 1;
>> auto __update = [&, __pos = __exts.rank()](_IndexType) mutable
>> {
>> --__pos;
>> _GLIBCXX_DEBUG_ASSERT(cmp_less(__ind_arr[__pos],
>> @@ -2673,118 +3140,86 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> else if constexpr (__is_constant_wrapper<_Slice>
>> && _Extent != dynamic_extent)
>> static_assert(std::cmp_less(_Slice::value, _Extent));
>> else if constexpr (convertible_to<_Slice, _IndexType>)
>> __glibcxx_assert(__slice < __ext.extent(0));
>> }
>>
>> template<typename _Extents, typename... _Slices>
>> constexpr void
>> __check_valid_slices(const _Extents& __exts, const _Slices&...
>> __slices)
>> {
>> constexpr auto __rank = _Extents::rank();
>> auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>)
>> {
>> ((__mdspan::__check_valid_slice(__extract_extent<_Is>(__exts),
>> __slices...[_Is])),...);
>> };
>> __impl(make_index_sequence<__rank>());
>> }
>>
>> - template<typename _IndexType, size_t _Extent, typename _Slice>
>> - consteval size_t
>> - __static_slice_extent()
>>
>
>
>> + template<typename _Mapping>
>> + concept __sliceable_mapping = requires(_Mapping __m)
>> {
>> - if constexpr (same_as<_Slice, full_extent_t>)
>> - return _Extent;
>> - else if constexpr (same_as<_Slice,
>> constant_wrapper<_IndexType(0)>>)
>> - return 0;
>> - else if constexpr (__is_constant_wrapper<typename
>> _Slice::extent_type>
>> - && __is_constant_wrapper<typename
>> _Slice::stride_type>)
>> - return 1 + ((typename _Slice::extent_type{}) - 1)
>> - / (typename _Slice::stride_type{});
>> - else
>> - return dynamic_extent;
>> - }
>> -
>> - template<size_t _K, typename _Extents, typename _Slice>
>> - constexpr typename _Extents::index_type
>> - __dynamic_slice_extent(const _Extents& __exts, _Slice __slice)
>> - {
>> - if constexpr (__is_strided_slice<_Slice>)
>> - return __slice.extent == 0 ? 0 : 1 + (__slice.extent - 1) /
>> __slice.stride;
>> - else
>> - return __exts.extent(_K);
>> - }
>> -
>> - template<typename _IndexType, size_t... _Extents, typename... _Slices>
>> - requires (sizeof...(_Slices) == sizeof...(_Extents))
>> - constexpr auto
>> - __subextents(const extents<_IndexType, _Extents...>& __exts,
>> - _Slices... __slices)
>> - {
>> - constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType,
>> _Slices...>();
>> - auto __impl = [&]<size_t... _Indices>(index_sequence<_Indices...>)
>> {
>> - using _SubExtents = extents<_IndexType,
>> - (__mdspan::__static_slice_extent<_IndexType,
>> - _Extents...[__inv_map[_Indices]],
>> - _Slices...[__inv_map[_Indices]]>())...>;
>> - if constexpr (_SubExtents::rank_dynamic() == 0)
>> - return _SubExtents{};
>> - else
>> - {
>> - using _StaticSubExtents = __mdspan::_StaticExtents<
>> - __mdspan::__static_extents<_SubExtents>()>;
>> - auto __create = [&]<size_t... _Is>(index_sequence<_Is...>)
>> - {
>> - constexpr auto __slice_idx = [__inv_map](size_t __i)
>> consteval
>> - {
>> - return
>> __inv_map[_StaticSubExtents::_S_dynamic_index_inv(__i)];
>> - };
>> -
>> - return _SubExtents{
>> - (__mdspan::__dynamic_slice_extent<__slice_idx(_Is)>(
>> - __exts, __slices...[__slice_idx(_Is)]))...};
>> - };
>> - constexpr auto __dyn_subrank = _SubExtents::rank_dynamic();
>> - return __create(make_index_sequence<__dyn_subrank>());
>> - }
>> - };
>> -
>> - return __impl(make_index_sequence<__inv_map.size()>());
>> - }
>> + [__m]<size_t... _Is>(index_sequence<_Is...>)
>> + {
>> + return submdspan_mapping(__m, ((void) _Is, full_extent)...);
>> + }(make_index_sequence<_Mapping::extents_type::rank()>())
>>
> This is not doing the check correctly, as the lambda body is not considered
> part of SFINAE.
> (Please add a test that on mdspan with custom mapping without submapping,
> doing
> require { submdspan(m, slices....); }
> returns false.
>
> To bring this as part of SFINAE, we should put the call inside the return
> type of lamba:
> [__m]<size_t... _Is>(index_sequence<_Is...>)
> -> decltype(submdspan_mapping(__m, ((void) _Is, full_extent)...))
> { return submdspan_mapping(__m, ((void) _Is, full_extent)...); }
>
> template<typename _Mapping, typename... Slices>
> concept __sliceable_mapping = requires(_Mapping __m, Slices... slices)
> {
> { submdspan_mapping(__m, slices...); } -> __submdspan_mapping_result;
> }
>
> And then checking __mdspan::__sliceable_mapping<typename
> _Layout::mapping<_Extents>, __mdspan::__full_extent_t<Slices>...>.
> This will give much more readable error message.
>
>> + } -> __submdspan_mapping_result;
>> + };
>> }
>>
>> template<typename _IndexType, size_t... _Extents, typename...
>> _RawSlices>
>> requires (sizeof...(_RawSlices) == sizeof...(_Extents))
>> constexpr auto
>> submdspan_extents(const extents<_IndexType, _Extents...>& __exts,
>> _RawSlices... __raw_slices)
>> {
>> auto __impl = [&__exts](auto... __slices)
>> {
>> __mdspan::__check_valid_slices(__exts, __slices...);
>> return __mdspan::__subextents(__exts, __slices...);
>> };
>> return __impl(__mdspan::__slice_cast<_IndexType>(__raw_slices)...);
>> }
>>
>> template<typename _IndexType, size_t... _Extents, typename...
>> _RawSlices>
>> requires (sizeof...(_Extents) == sizeof...(_RawSlices))
>> constexpr auto
>> submdspan_canonicalize_slices(const extents<_IndexType, _Extents...>&
>> __exts,
>> _RawSlices... __raw_slices)
>> {
>> auto __impl = [&__exts](auto... __slices)
>> {
>> __mdspan::__check_valid_slices(__exts, __slices...);
>> return std::make_tuple(__slices...);
>> };
>> return __impl(__mdspan::__slice_cast<_IndexType>(__raw_slices)...);
>> }
>> +
>> + template<typename _ElementType, typename _Extents, typename _Layout,
>> + typename _Accessor, typename... _RawSlices>
>> + requires (sizeof...(_RawSlices) == _Extents::rank()
>> + && __mdspan::__sliceable_mapping<typename
>> _Layout::mapping<_Extents>>)
>> + constexpr auto
>> + submdspan(
>> + const mdspan<_ElementType, _Extents, _Layout, _Accessor>& __md,
>> + _RawSlices... __raw_slices)
>> + {
>> + using _IndexType = typename _Extents::index_type;
>> + auto __impl = [&__md](auto... __slices)
>> + {
>> + using __mdspan::submdspan_mapping;
>>
> I would define the __subbmapping function inside __mdspan below the concet
> that will return submdspan_mapping_result.
> This will avoid the need for this using:
> template<typename _Mapping, typename... Slices>
> constexpr auto __submapping(const _Mapping& __map, _Slices... slices)
> {
> __mdspan::__check_valid_slices(__md.extents(), __slices...);
> return __result = submdspan_mapping(__md.mapping(), __slices...);
> }
>
>
>
>> + __mdspan::__check_valid_slices(__md.extents(), __slices...);
>> + auto __result = submdspan_mapping(__md.mapping(), __slices...);
>>
> I would use auto [__mapping, __offset] = submdspan_mapping(__md.mapping(),
> __slices...) here.
>
>> + return std::mdspan(__md.accessor().offset(__md.data_handle(),
>> __result.offset),
>> + __result.mapping, typename
>> _Accessor::offset_policy(__md.accessor()));
>>
> I would move mapping here, the user defined one may have more efficient
> moves.
>
>> + };
>> + return __impl(__mdspan::__slice_cast<_IndexType>(__raw_slices)...);
>> + }
>> #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
>> @@ -1868,42 +1868,42 @@ export namespace std
>> #endif
>> using std::layout_left;
>> using std::layout_right;
>> using std::layout_stride;
>> using std::default_accessor;
>> #if __glibcxx_aligned_accessor
>> using std::aligned_accessor;
>> #endif
>> using std::mdspan;
>> #if __glibcxx_padded_layouts
>> using std::layout_left_padded;
>> using std::layout_right_padded;
>> #endif
>> #if __glibcxx_submdspan
>> using std::strided_slice;
>> using std::full_extent_t;
>> using std::full_extent;
>> using std::submdspan_mapping_result;
>> using std::submdspan_canonicalize_slices;
>> using std::submdspan_extents;
>> + 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;
>> using std::allocator_traits;
>> using std::assume_aligned;
>> #if __glibcxx_is_sufficiently_aligned
>> using std::is_sufficiently_aligned;
>> #endif
>> using std::make_obj_using_allocator;
>> using std::pointer_traits;
>> using std::to_address;
>> using std::uninitialized_construct_using_allocator;
>> using std::uses_allocator;
>> 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 } }
>>
> This test is already timeout out in the debug mode, my suggestion would be,
> create and mdspan dir, where you will have the test machinery in header,
> and then inside files for instantiating for each layout, with separation
> for
> cheap and expensive tests. I will later in separate commit.
> We should also add, to the test.
> // { dg-timeout-factor 2.0 }
>
> I will then increase value as needed.
>
> +#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
>>
> These are usually named sentinel.
> I would just name it struct sentinel
>
>> + { };
>> +
>> + class BeginIt
>>
> // And this, just name the iterator.
>
>> + {
>> + 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)
>>
> Making this hidden friend:
> constexpr friend bool operator==(const BeginIt&, _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)
>
> Instead of creating vector of array, I would do:
> multi_index_generator gen(shape);
> auto it = gen.begin();
> auto end = gen.end();
>
> And then inside loop:
> {
> VERIFY(it != end);
> VERIFY(*it == std::array{i, j, k, l});
> ++it;
> }
>
>
>> + 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..cdc8a2b7e23
>> --- /dev/null
>> +++
>> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc
>> @@ -0,0 +1,161 @@
>> +// { 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" }
>> +
>> +namespace adl
>> +{
>> + struct NoFull
>>
> Add test with no submdpsan_mapping at all,
> it should then be checkable with requires { submdspan(mapping, slice,
> slice); }
> Test will only one dimension should be fine.
>
>> + {
>> + template<typename Extents>
>> + class mapping
>> + {
>> + public:
>> + using extents_type = Extents;
>> + using index_type = typename extents_type::index_type;
>> +
>> + private:
>> + friend constexpr auto
>> + submdspan_mapping(mapping, int)
>> + { return std::submdspan_mapping_result{mapping{}, 0}; }
>> + };
>> + };
>> +
>> + struct WrongReturnValue
>> + {
>> + template<typename Extents>
>> + class mapping
>> + {
>> + public:
>> + using extents_type = Extents;
>> + using index_type = typename extents_type::index_type;
>> +
>> + private:
>> + friend constexpr int
>> + submdspan_mapping(mapping, std::full_extent_t)
>> + { return 42; }
>> + };
>> + };
>> +}
>> +
>> +constexpr bool
>> +test_invalid_mapping1()
>> +{
>> + using Extents = std::extents<int, 3>;
>> + auto map = adl::NoFull::mapping<Extents>{};
>> + std::mdspan<double, Extents, adl::NoFull> m(nullptr, map);
>> + std::submdspan(m, 1); // { dg-error "no matching function" }
>> + return true;
>> +}
>> +static_assert(test_invalid_mapping1());
>> +
>> +constexpr bool
>> +test_invalid_mapping2()
>> +{
>> + using Extents = std::extents<int, 3>;
>> + auto map = adl::WrongReturnValue::mapping<Extents>{};
>> + std::mdspan<double, Extents, adl::WrongReturnValue> m(nullptr, map);
>> + std::submdspan(m, std::full_extent); // { dg-error "no matching
>> function" }
>> + return true;
>> +}
>> +static_assert(test_invalid_mapping2());
>> +
>> +// { dg-prune-output "static assertion failed" }
>> +// { dg-prune-output "__glibcxx_assert_fail" }
>> +// { dg-prune-output "non-constant condition" }
>> +// { dg-prune-output "no matching function" }
>> +// { dg-prune-output "does not satisfy placeholder constraints" }
>> --
>> 2.52.0
>>
>>
>
More information about the Libstdc++
mailing list