libstdc++: Implement submdspan and submdspan_mapping for layout_left. [PR110352]

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 (__mdspan::__is_submdspan_mapping_result)
	(__mdspan::__submdspan_mapping_result, __mdspan::__fwd_prod)
	(__mdspan::__acceptable_slice_type, __mdspan::__slice_begin)
	(__mdspan::__suboffset, __mdspan::_LayoutSide, __mdspan::__mapping_side)
	(__mdspan::_StridesTrait, __mdspan::__substrides_generic)
	(__mdspan::__substrides_standardized, __mdspan::__substrides)
	(__mdspan::__is_unit_stride_slice, __mdspan::_SliceKind)
	(__mdspan::__make_slice_kind, __mdspan::__make_slice_kind_array)
	(__mdspan::__is_block, __mdspan::__padded_block_begin_generic)
	(__mdspan::__padded_block_begin, __mpdspan::_SubMdspanMapping)
	(__mdspan::__submdspan_mapping_impl): Define.
	(__mdspan::__dynamic_slice_extent, __mdspan::__static_slice_extent)
	(__mdspan::__subextents): Move earlier in the file.
	(layout_left::mapping::submdspan_mapping, __mdspan::__sliceable_mapping)
	(__mdspan::__submapping, submdspan): Define.
	* src/c++23/std.cc.in: Add submdspan.
	* testsuite/23_containers/mdspan/submdspan/generic.cc: New test.
	* testsuite/23_containers/mdspan/submdspan/selections/left.cc:
	Instantiate selection tests for layout_left.
	* testsuite/23_containers/mdspan/submdspan/selections/testcases.h: Generic
	tests different selections.
	* testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc: New test.
	* testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc: New test.

Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
Signed-off-by: Luc Grosheintz <luc.grosheintz@gmail.com>
This commit is contained in:
Luc Grosheintz
2025-12-08 21:23:41 +01:00
committed by Tomasz Kamiński
parent 49c4b73283
commit ead579d3c5
7 changed files with 1184 additions and 59 deletions

View File

@@ -374,6 +374,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
[[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>
@@ -589,6 +599,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
}
}
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
@@ -866,6 +886,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__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()
@@ -890,6 +915,443 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__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)>)
{
if (__slice.stride < __slice.extent)
__ret[__krev] = __stride * __slice.stride;
else
__ret[__krev] = __stride;
}
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>()...};
}
// __block_size - 1
// [full, ..., full, unit_slice , *]
consteval bool
__is_block(span<const _SliceKind> __slice_kinds, size_t __block_size)
{
if (__block_size == 0)
return false;
if (__block_size > __slice_kinds.size())
return false;
for (size_t __i = 0; __i < __block_size - 1; ++__i)
if (__slice_kinds[__i] != _SliceKind::__full)
return false;
auto __last = __slice_kinds[__block_size - 1];
return __last == _SliceKind::__full
|| __last == _SliceKind::__unit_strided_slice;
}
// __u __u + __sub_rank-2
// [unit_slice, i, ..., k, full, ..., full, unit_slice, *]
static consteval size_t
__padded_block_begin_generic(span<const _SliceKind> __slice_kinds,
size_t __sub_rank)
{
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
{
size_t __u = 1;
while(__u < __slice_kinds.size()
&& __slice_kinds[__u] == _SliceKind::__collapsing)
++__u;
if (__mdspan::__is_block(__slice_kinds.subspan(__u), __sub_rank -1))
return __u;
return dynamic_extent;
}
}
template<_LayoutSide _Side, size_t _Nm>
static consteval size_t
__padded_block_begin(span<const _SliceKind, _Nm> __slice_kinds, size_t __sub_rank)
{
if constexpr (_Side == _LayoutSide::__left)
return __mdspan::__padded_block_begin_generic(__slice_kinds, __sub_rank);
else
{
std::array<_SliceKind, _Nm> __rev_slices;
for(size_t __i = 0; __i < _Nm; ++__i)
__rev_slices[__i] = __slice_kinds[_Nm - 1 - __i];
auto __rev_slice_kinds = span<const _SliceKind>(__rev_slices);
auto __u = __mdspan::__padded_block_begin_generic(__rev_slice_kinds,
__sub_rank);
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 _Nm>
static consteval bool
_S_is_unpadded_submdspan(span<const _SliceKind, _Nm> __slice_kinds, size_t __sub_rank)
{ return __mdspan::__is_block(__slice_kinds, __sub_rank); }
};
template<typename _Mapping>
constexpr auto
__submdspan_mapping_impl(const _Mapping& __mapping)
{ return submdspan_mapping_result{__mapping, 0}; }
template<typename _Mapping, typename... _Slices>
requires (sizeof...(_Slices) > 0)
constexpr auto
__submdspan_mapping_impl(const _Mapping& __mapping, _Slices... __slices)
{
using _IndexType = typename _Mapping::index_type;
static_assert((__acceptable_slice_type<_Slices, _IndexType> && ...));
constexpr auto __side = __mdspan::__mapping_side<_Mapping>();
constexpr auto __rank = sizeof...(_Slices);
using _Trait = _SubMdspanMapping<__side>;
using _SliceView = span<const _SliceKind, __rank>;
constexpr auto __slice_kinds = __mdspan::__make_slice_kind_array<_Slices...>();
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::_S_is_unpadded_submdspan(
_SliceView(__slice_kinds), __sub_rank))
return submdspan_mapping_result{
typename _Trait::_Layout::mapping(__sub_exts), __offset};
else if constexpr (
constexpr auto __u = __padded_block_begin<__side>(
_SliceView(__slice_kinds), __sub_rank);
__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};
}
}
#endif // __glibcxx_submdspan
}
@@ -1032,6 +1494,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__glibcxx_assert(__mdspan::__is_representable_extents(_M_extents));
}
#if __glibcxx_submdspan
template<typename... _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{};
};
@@ -2700,68 +3170,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__impl(make_index_sequence<__rank>());
}
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<typename _Slice>
using __full_extent_t = std::full_extent_t;
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);
}
// Enables ADL-only calls from submdspan.
void submdspan_mapping() = delete;
template<typename _IndexType, size_t... _Extents, typename... _Slices>
requires (sizeof...(_Slices) == sizeof...(_Extents))
template<typename _Mapping, typename... _Slices>
concept __sliceable_mapping = requires(const _Mapping __m, _Slices... __slices)
{
{ submdspan_mapping(__m, __slices...) } -> __submdspan_mapping_result;
};
template<typename _Mapping, typename... _Slices>
constexpr auto
__subextents(const extents<_IndexType, _Extents...>& __exts,
_Slices... __slices)
__submapping(const _Mapping& __mapping, _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()>());
__mdspan::__check_valid_slices(__mapping.extents(), __slices...);
return submdspan_mapping(__mapping, __slices...);
}
}
@@ -2792,6 +3218,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
};
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>,
__mdspan::__full_extent_t<_RawSlices>...>)
constexpr auto
submdspan(
const mdspan<_ElementType, _Extents, _Layout, _Accessor>& __md,
_RawSlices... __raw_slices)
{
using _IndexType = typename _Extents::index_type;
auto [__mapping, __offset] = __mdspan::__submapping(
__md.mapping(), __mdspan::__slice_cast<_IndexType>(__raw_slices)...);
return std::mdspan(
__md.accessor().offset(__md.data_handle(), __offset),
std::move(__mapping),
typename _Accessor::offset_policy(__md.accessor()));
}
#endif // __glibcxx_submdspan
_GLIBCXX_END_NAMESPACE_VERSION

View File

@@ -1899,8 +1899,8 @@ export namespace std
using std::submdspan_mapping_result;
using std::submdspan_canonicalize_slices;
using std::submdspan_extents;
using std::submdspan;
#endif
// FIXME mdsubspan
}
#endif

View File

@@ -0,0 +1,71 @@
// { dg-do compile { target c++26 } }
#include <mdspan>
namespace adl
{
struct NoFriend
{
template<typename Extents>
class mapping
{
public:
using extents_type = Extents;
using index_type = typename extents_type::index_type;
};
};
struct NoFull
{
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; }
};
};
}
template<typename MdSpan, typename... Slices>
concept submdspan_exists = requires (MdSpan md, Slices... slices)
{
std::submdspan(md, slices...);
};
template<typename Layout, bool Expected>
constexpr bool
test_invalid_mapping()
{
using Extents = std::extents<int, 3>;
using MdSpan = std::mdspan<double, Extents, Layout>;
static_assert(submdspan_exists<MdSpan, int> == Expected);
static_assert(submdspan_exists<MdSpan, std::full_extent_t> == Expected);
static_assert(!submdspan_exists<MdSpan>);
static_assert(!submdspan_exists<MdSpan, int, int>);
return true;
}
static_assert(test_invalid_mapping<std::layout_left, true>());
static_assert(test_invalid_mapping<adl::NoFriend, false>());
static_assert(test_invalid_mapping<adl::NoFull, false>());
static_assert(test_invalid_mapping<adl::WrongReturnValue, false>());

View File

@@ -0,0 +1,9 @@
// { dg-do run { target c++26 } }
#include "testcases.h"
int
main()
{
test_all<std::layout_left>();
return 0;
}

View File

@@ -0,0 +1,360 @@
#include <mdspan>
#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
{
struct sentinel
{ };
class iterator
{
public:
constexpr
iterator(const std::array<Int, Rank>& shape)
: M_shape(shape)
{ }
constexpr iterator&
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; }
private:
friend constexpr bool
operator==(const iterator& it, sentinel)
{
if constexpr (Rank > 0)
return it.M_indices[0] == it.M_shape[0];
else
return true;
}
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 iterator
begin() const
{ return iterator(M_shape); }
constexpr sentinel
end() const
{ return sentinel{}; }
private:
std::array<Int, Rank> M_shape;
};
constexpr bool
test_multi_index()
{
auto shape = std::array{3, 5, 7, 1};
auto gen = multi_index_generator(shape);
auto it = gen.begin();
auto end = gen.end();
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)
{
VERIFY(it != end);
VERIFY(*it == std::array{i, j, k, l});
++it;
}
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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,104 @@
// { 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" }
// { dg-prune-output "no matching function" }
// { dg-prune-output "does not satisfy placeholder constraints" }