[PATCH v1 4/6] libstdc++: Implement submdspan and submdspan_mapping for layout_left. [PR110352]

Luc Grosheintz luc.grosheintz@gmail.com
Sat Nov 15 11:52:22 GMT 2025



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 <luc.grosheintz@gmail.com>
> ---
>   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.)

>   	    ((__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" }



More information about the Libstdc++ mailing list