On Thu, Oct 2, 2025 at 11:36 AM Luc Grosheintz <[email protected]> wrote:
> Implement submdspan_extents as described in N5014 and adds it to the std > module. > > The implementation contains a casting or canonicalization layer. > Similar, but different to the one described in P3663. It's purpose is > twofold: > > - Reduce the number of template instantiations and isolate the > complexity of dealing with user-defined integer like types to this > layer. > > - Handle special user-defined types that only convert to index_type > as rvalues, etc. > > The rules for canonicalization are: > > - collapsing slices, i.e. those that convert to index_type, are > canonicalized as follows: > > + integral-constant-like objects are canonicalized to the corresponding > integral_constant<index_type, ...> object, > > + everything else is canonicalized via > static_cast<index_type>(std::move(...)), > > - for any strided_slice it's the analogous strided_slice with all > three members canonicalized, > > - index-pair-like objects are converted to std::tuple with both > elements canonicalized, > > - anything convertable to full_extent_t is canonicalized to > full_extent. > > PR libstdc++/110352 > > libstdc++-v3/ChangeLog: > > * include/std/mdspan (submdspan_extents): New function. > * testsuite/23_containers/mdspan/int_like.h: Add StructuralInt. > * testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc: > New test. > * > testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc: New test. > > Signed-off-by: Luc Grosheintz <[email protected]> > --- > libstdc++-v3/include/std/mdspan | 276 ++++++++++++++++++ > .../testsuite/23_containers/mdspan/int_like.h | 9 + > .../mdspan/submdspan/submdspan_extents.cc | 159 ++++++++++ > .../mdspan/submdspan/submdspan_extents_neg.cc | 66 +++++ > 4 files changed, 510 insertions(+) > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc > > diff --git a/libstdc++-v3/include/std/mdspan > b/libstdc++-v3/include/std/mdspan > index cef6d625982..a88d4f8ff76 100644 > --- a/libstdc++-v3/include/std/mdspan > +++ b/libstdc++-v3/include/std/mdspan > @@ -40,6 +40,7 @@ > > #if __cplusplus > 202302L > #include <bits/align.h> > +#include <tuple> > #endif > > #define __glibcxx_want_mdspan > @@ -368,6 +369,123 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > [[no_unique_address]] _Mapping mapping = _Mapping(); > size_t offset{}; > }; > + > + namespace __mdspan > + { > + template<typename _Tp> > + constexpr _Tp > + __unwrap_constant(_Tp __value) > + { return __value; } > + > + template<__detail::__integral_constant_like _Tp> > + constexpr auto > + __unwrap_constant(_Tp) > + { return _Tp::value; } > + > + template<typename _Tp, typename _IndexType> > + concept __index_pair_like = __pair_like<_Tp> > + && convertible_to<tuple_element_t<0,_Tp>, _IndexType> > + && convertible_to<tuple_element_t<1,_Tp>, _IndexType>; > + > + template<typename _Tp> > + inline constexpr bool __is_strided_slice = false; > + > + template<typename _OffsetType, typename _ExtentType, typename > _StrideType> > + inline constexpr bool __is_strided_slice<strided_slice<_OffsetType, > + _ExtentType, _StrideType>> = true; > + > + template<typename _IndexType, typename... _Slices> > + consteval auto __submdspan_rank() > + { > + 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 = __submdspan_rank<_IndexType, > _Slices...>(); > + auto __map = std::array<size_t, __sub_rank>{}; > + auto __is_int_like = std::array{convertible_to<_Slices, > _IndexType>...}; > + > + size_t __i = 0; > + for (size_t __k = 0; __k < __rank; ++__k) > + if (!__is_int_like[__k]) > + __map[__i++] = __k; > + return __map; > + } > + > + template<typename _IndexType, typename _Slice> > + constexpr auto > + __slice_cast(_Slice&& __slice) > + { > + if constexpr (convertible_to<_Slice, _IndexType> > + && __detail::__integral_constant_like<_Slice>) > + return integral_constant<_IndexType, > _IndexType{_Slice::value}>{}; > + else if constexpr (convertible_to<_Slice, _IndexType>) > + return __index_type_cast<_IndexType>(std::move(__slice)); > + else if constexpr (__index_pair_like<_Slice, _IndexType>) > + { > + auto [__begin, __end] = std::move(__slice); > + return std::tuple{__slice_cast<_IndexType>(std::move(__begin)), > + __slice_cast<_IndexType>(std::move(__end))}; > + } > + else if constexpr (__is_strided_slice<_Slice>) > + return strided_slice{ > + __slice_cast<_IndexType>(std::move(__slice.offset)), > + __slice_cast<_IndexType>(std::move(__slice.extent)), > + __slice_cast<_IndexType>(std::move(__slice.stride))}; > + else > + return full_extent; > + } > + > + template<typename _IndexType, typename _Slice> > + constexpr _IndexType > + __slice_begin(_Slice __slice) > + { > + if constexpr (convertible_to<_Slice, _IndexType>) > + return __slice; > + else if constexpr (__index_pair_like<_Slice, _IndexType>) > + return get<0>(__slice); > + else if constexpr (__is_strided_slice<_Slice>) > + return __slice.offset; > + else > + return 0; // full_extent > + } > + > + template<size_t _K, typename _Extents, typename _Slice> > + constexpr typename _Extents::index_type > + __slice_end(_Extents __exts, _Slice __slice) > + { > + using _IndexType = typename _Extents::index_type; > + if constexpr (convertible_to<_Slice, _IndexType>) > + return __unwrap_constant(__slice) + 1; > + else if constexpr (__index_pair_like<_Slice, _IndexType>) > + return get<1>(__slice); > + else if constexpr (__is_strided_slice<_Slice>) > + return __unwrap_constant(__slice.offset) > + + __unwrap_constant(__slice.extent); > + else > + return __exts.extent(_K); // full_extent > + } > + > + template<size_t _K, typename _Extents, typename _Slice> > + constexpr typename _Extents::index_type > + __slice_size(const _Extents& __exts, _Slice __slice) > + { > + using _IndexType = typename _Extents::index_type; > + if constexpr (convertible_to<_Slice, _IndexType>) > + return 1; > + else if constexpr (__index_pair_like<_Slice, _IndexType>) > + return __unwrap_constant(get<1>(__slice)) > + - __unwrap_constant(get<0>(__slice)); > + else if constexpr (__is_strided_slice<_Slice>) > + return __unwrap_constant(__slice.extent); > + else > + return __exts.extent(_K); > + } > + } > #endif > Add __glibcxx_submdspan comment, or move this internal functions to the __mdspan namespace before sumdspan. I preffer the latter. > > template<typename _IndexType, size_t... _Extents> > @@ -2492,6 +2610,164 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > typename _MappingType::extents_type, > typename _MappingType::layout_type, _AccessorType>; > > +#if __glibcxx_submdspan > + namespace __mdspan > + { > + template<typename _Slice> > + consteval bool > + __is_statically_empty_strided_slice() > + { > + if constexpr (__is_strided_slice<_Slice>) > + { > + using _ExtentType = typename _Slice::extent_type; > + return __detail::__integral_constant_like<_ExtentType> > + && _ExtentType() == 0; > + } > + else > + return false; > + } > + > + template<typename _Slice> > + consteval bool > + __is_statically_sized_strided_slice() > + { > + if constexpr (__is_strided_slice<_Slice>) > + { > + using _ExtentType = typename _Slice::extent_type; > + using _StrideType = typename _Slice::stride_type; > + return __detail::__integral_constant_like<_ExtentType> > + && __detail::__integral_constant_like<_StrideType>; > + } > + else > + return false; > + } > + > + template<typename _IndexType, typename _Slice> > + consteval bool > + __is_statically_sized_tuple() > + { > + if constexpr (__index_pair_like<_Slice, _IndexType>) > + return __detail::__integral_constant_like<tuple_element_t<0, > _Slice>> > + && __detail::__integral_constant_like<tuple_element_t<1, > _Slice>>; > + else > + return false; > + } > + > + > + template<typename _IndexType, size_t _Extent, typename _Slice> > + consteval size_t > + __deduce_static_slice_extent() > + { > + if constexpr (is_convertible_v<_Slice, full_extent_t>) > + return _Extent; > + else if constexpr (__is_statically_sized_tuple<_IndexType, > _Slice>()) > + return __unwrap_constant(tuple_element_t<1, _Slice>()) > + - __unwrap_constant(tuple_element_t<0, _Slice>()); > + else if constexpr (__is_statically_empty_strided_slice<_Slice>()) > + return 0; > + else if constexpr (__is_statically_sized_strided_slice<_Slice>()) > + return 1 + (__unwrap_constant(typename _Slice::extent_type()) - > 1) > + / __unwrap_constant(typename _Slice::stride_type()); > + else > + return dynamic_extent; > + } > + > + template<size_t _K, typename _Extents, typename _Slice> > + constexpr typename _Extents::index_type > + __deduce_dynamic_slice_extent(const _Extents& __exts, _Slice > __slice) > + { > + if constexpr (__is_strided_slice<_Slice>) > + return __slice.extent == 0 ? 0 : > + 1 + (__unwrap_constant(__slice.extent) - 1) > + / __unwrap_constant(__slice.stride); > + else > + return __slice_size<_K>(__exts, __slice); > + } > + > + template<typename _Slice, typename _IndexType> > + concept __valid_slice_type = convertible_to<_Slice, _IndexType> > + + __index_pair_like<_Slice, _IndexType> > + + is_convertible_v<_Slice, full_extent_t> > + + __is_strided_slice<_Slice> == 1; > + > + template<size_t _K, typename _Extents, typename _Slice> > + constexpr bool > + __is_valid_slice(const _Extents& __exts, const _Slice& __slice) > + { > + using _IndexType = typename _Extents::index_type; > + if constexpr (__is_strided_slice<_Slice>) > + if (__slice.extent != 0 && __slice.stride == 0) > + return false; > + > + auto __begin = __slice_begin<_IndexType>(__slice); > + auto __end = __slice_end<_K>(__exts, __slice); > + return std::cmp_less_equal(__begin, __end) > + && std::cmp_less_equal(__end, __exts.extent(_K)); > + } > + > + template<typename _Extents, typename... _Slices> > + constexpr bool > + __all_valid_slices(const _Extents& __exts, const _Slices&... > __slices) > + { > + auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>){ > + return (__is_valid_slice<_Is>(__exts, __slices...[_Is]) && ...); > + }; > + return __impl(make_index_sequence<sizeof...(_Slices)>()); > + } > + > + > + template<typename _IndexType, size_t... _Extents, typename... _Slices> > + requires (sizeof...(_Slices) == sizeof...(_Extents)) > + constexpr auto __submdspan_extents( > + const extents<_IndexType, _Extents...>& __exts, _Slices... > __slices) > + { > + __glibcxx_assert(__mdspan::__all_valid_slices(__exts, > __slices...)); > + > + constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType, > + _Slices...>(); > + auto __impl = [&]<size_t... _Indices>(index_sequence<_Indices...>) > + { > + using _SubExtents = extents<_IndexType, > + (__mdspan::__deduce_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::__deduce_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()>()); > + } > + } > + > + template<typename _IndexType, size_t... _Extents, typename... _Slices> > + requires (sizeof...(_Slices) == sizeof...(_Extents)) > + constexpr auto submdspan_extents( > + const extents<_IndexType, _Extents...>& __exts, _Slices... > __slices) > + { > + static_assert((__mdspan::__valid_slice_type<_Slices, _IndexType> && > ...)); > + return __mdspan::__submdspan_extents(__exts, > + __mdspan::__slice_cast<_IndexType>(std::move(__slices))...); > + } > +#endif > Add __glibcxx_submdspan comment. > + > _GLIBCXX_END_NAMESPACE_VERSION > } > #endif > diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h > b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h > index e9172c13455..67c759245f9 100644 > --- a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h > @@ -60,4 +60,13 @@ using RValueInt = > CustomIndexType<CustomIndexKind::RValue>; > struct NotIntLike > { }; > > +struct StructuralInt > +{ > + constexpr > + operator int() const noexcept > + { return value; } > + > + int value; > +}; > + > #endif // TEST_MDSPAN_INT_LIKE_H > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc > new file mode 100644 > index 00000000000..a20509a7fc6 > --- /dev/null > +++ > b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc > @@ -0,0 +1,159 @@ > +// { dg-do run { target c++26 } } > +#include <mdspan> > +#include <tuple> > + > +#include "../int_like.h" > +#include <testsuite_hooks.h> > + > +constexpr size_t dyn = std::dynamic_extent; > +constexpr auto all = std::full_extent; > + > +constexpr bool > +test_from_full_extent() > +{ > + auto exts = std::extents<int, 3, dyn, 7>{}; > + auto sub_exts = submdspan_extents(exts, 1, all, all); > + VERIFY(sub_exts.rank() == 2); > + VERIFY(sub_exts.static_extent(0) == dyn); > + VERIFY(sub_exts.extent(0) == exts.extent(1)); > + VERIFY(std::cmp_equal(sub_exts.static_extent(1), exts.extent(2))); > + return true; > +} > + > +template<template<int> typename Cw> > + constexpr bool > + test_from_tuple() > + { > + auto exts = std::extents<int, 3, 5, 7>{}; > + auto s0 = Cw<1>{}; > + auto s1 = std::tuple{Cw<1>{}, Cw<2>{}}; > + auto s2 = std::tuple{Cw<1>{}, 4}; > + auto sub_exts = submdspan_extents(exts, s0, s1, s2); > + VERIFY(sub_exts.rank() == 2); > + VERIFY(sub_exts.static_extent(0) == size_t(get<1>(s1) - get<0>(s1))); > + VERIFY(sub_exts.static_extent(1) == dyn); > + VERIFY(std::cmp_equal(sub_exts.extent(1), get<1>(s2) - get<0>(s2))); > + return true; > + } > + > +template<typename Int> > + bool > + test_from_int_like_as_scalar() > + { > + auto exts = std::extents<int, 3, 5>{}; > + auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{1, 3}); > + VERIFY(sub_exts.rank() == 1); > + VERIFY(sub_exts.static_extent(0) == dyn); > + VERIFY(sub_exts.extent(0) == 2); > + return true; > + } > + > +template<typename Int> > + bool > + test_from_const_int() > + { > + auto exts = std::extents<int, 3, 5>{}; > + auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{1, 3}); > + VERIFY(sub_exts.rank() == 1); > + VERIFY(sub_exts.static_extent(0) == dyn); > + VERIFY(sub_exts.extent(0) == 2); > + return true; > + } > + > +template<typename Int> > + constexpr bool > + test_from_int_like_in_tuple() > + { > + auto exts = std::extents<int, 3, 5>{}; > + auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{Int(1), > Int(3)}); > + VERIFY(sub_exts.rank() == 1); > + VERIFY(sub_exts.static_extent(0) == dyn); > + VERIFY(sub_exts.extent(0) == 2); > + return true; > + } > + > +template<template<int> typename Cw> > + constexpr bool > + test_from_strided_slice() > + { > + auto exts = std::extents<int, 5, 7, 11>{}; > + { > + auto s0 = 1; > + auto s1 = std::strided_slice{0, 0, 0}; > + auto s2 = std::strided_slice{1, Cw<0>{}, 0}; > + auto sub_exts = submdspan_extents(exts, s0, s1, s2); > + VERIFY(sub_exts.rank() == 2); > + VERIFY(sub_exts.static_extent(0) == dyn); > + VERIFY(sub_exts.extent(0) == 0); > + VERIFY(sub_exts.static_extent(1) == 0); > + } > + > + { > + auto s0 = 1; > + auto s1 = std::strided_slice{0, 2, Cw<1>{}}; > + auto s2 = std::strided_slice{1, Cw<2>{}, 1}; > + auto sub_exts = submdspan_extents(exts, s0, s1, s2); > + VERIFY(sub_exts.rank() == 2); > + VERIFY(sub_exts.static_extent(0) == dyn); > + VERIFY(sub_exts.extent(0) == 2); > + VERIFY(sub_exts.static_extent(1) == dyn); > + VERIFY(sub_exts.extent(1) == 2); > + } > + > + { > + // selected = 1 x [1, 3] x [1, 4, 7, 10] > + auto s0 = 1; > + auto s1 = std::strided_slice{1, Cw<4>{}, 2}; > + auto s2 = std::strided_slice{1, Cw<10>{}, Cw<3>{}}; > + auto sub_exts = submdspan_extents(exts, s0, s1, s2); > + VERIFY(sub_exts.rank() == 2); > + VERIFY(sub_exts.static_extent(0) == dyn); > + VERIFY(sub_exts.extent(0) == 2); > + VERIFY(sub_exts.static_extent(1) == 4); > + } > + > + { > + // selected = [0, 2] x [1, 3] x [0, 3, 6] > + auto s0 = std::strided_slice(0, 3, 2); > + auto s1 = std::strided_slice(1, 4, 2); > + auto s2 = std::strided_slice(0, 7, 3); > + auto sub_exts = submdspan_extents(exts, s0, s1, s2); > + VERIFY(sub_exts.rank() == 3); > + VERIFY(sub_exts.extent(0) == 2); > + VERIFY(sub_exts.extent(1) == 2); > + VERIFY(sub_exts.extent(2) == 3); > + } > + return true; > + } > + > +template<int Value> > + using CW = std::constant_wrapper<Value, int>; > + > +template<int Value> > + using IC = std::integral_constant<int, Value>; > + > +constexpr bool > +test_all() > +{ > + test_from_full_extent(); > + test_from_tuple<CW>(); > + test_from_tuple<IC>(); > + test_from_strided_slice<CW>(); > + test_from_strided_slice<IC>(); > + test_from_int_like_in_tuple<StructuralInt>(); > + return true; > +} > + > +int > +main() > +{ > + test_all(); > + static_assert(test_all()); > + > + test_from_int_like_as_scalar<IntLike>(); > + test_from_int_like_as_scalar<MutatingInt>(); > + test_from_int_like_as_scalar<ThrowingInt>(); > + test_from_int_like_as_scalar<RValueInt>(); > + test_from_int_like_as_scalar<StructuralInt>(); > + return 0; > +} > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc > new file mode 100644 > index 00000000000..dca294c185e > --- /dev/null > +++ > b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc > @@ -0,0 +1,66 @@ > +// { dg-do compile { target c++26 } } > +#include <mdspan> > + > +#include <cstdint> > + > +struct NotASlice > +{ }; > + > +struct AmbiguousSlice > +{ > + constexpr > + operator int() const > + { return 0; } > + > + constexpr > + operator std::full_extent_t() const > + { return std::full_extent; } > +}; > + > +constexpr bool > +test_unrelated_stride_type() > +{ > + auto exts = std::extents(3, 5, 7); > + auto sub_exts = submdspan_extents(exts, 1, NotASlice{}, 2); // { > dg-error "required from" } > + return true; > +} > +static_assert(test_unrelated_stride_type()); > + > +constexpr bool > +test_ambiguous_stride_type() > +{ > + auto exts = std::extents(3, 5, 7); > + auto sub_exts = submdspan_extents(exts, 1, AmbiguousSlice{}, 2); // { > dg-error "required from" } > + return true; > +} > +static_assert(test_ambiguous_stride_type()); > + > +constexpr bool > +test_invalid_stride_zero() > +{ > + auto exts = std::extents(3, 5, 7); > + auto sub_exts = submdspan_extents(exts, 1, std::strided_slice{0, 1, 0}, > 2); // { dg-error "expansion of" } > + return true; > +} > +static_assert(test_invalid_stride_zero()); > + > +template<typename Slice> > +constexpr bool > +test_out_of_bounds_selection(const Slice& slice) > +{ > + auto exts = std::extents<uint16_t, 3, 5, 7>{}; > + auto sub_exts = submdspan_extents(exts, 1, slice, 2); // { dg-error > "expansion of" } > + return true; > +} > +static_assert(test_out_of_bounds_selection(std::strided_slice{0, 6, > 1})); // { dg-error "expansion of" } > +static_assert(test_out_of_bounds_selection(std::strided_slice{0, 7, > 2})); // { dg-error "expansion of" } > +static_assert(test_out_of_bounds_selection(std::strided_slice{1, 6, > 1})); // { dg-error "expansion of" } > +static_assert(test_out_of_bounds_selection(std::strided_slice{1, 6, > 2})); // { dg-error "expansion of" } > +static_assert(test_out_of_bounds_selection(std::tuple{1, 6})); > // { dg-error "expansion of" } > +static_assert(test_out_of_bounds_selection(std::tuple{std::cw<1>, > std::cw<6>})); // { dg-error "expansion of" } > +static_assert(test_out_of_bounds_selection(std::strided_slice{-1, 2, > 1})); // { dg-error "expansion of" } > +static_assert(test_out_of_bounds_selection(std::tuple{-1, 2})); > // { dg-error "expansion of" } > + > +// { dg-prune-output "static assertion failed" } > +// { dg-prune-output "__glibcxx_assert_fail" } > +// { dg-prune-output "non-constant condition" } > -- > 2.50.1 > >
