On Tue, Dec 2, 2025 at 9:10 AM Tomasz Kaminski <[email protected]> wrote:
> > > On Mon, Dec 1, 2025 at 9:43 AM Luc Grosheintz <[email protected]> > wrote: > >> Implement submdspan_extents as described in P3663 and adds it to the std >> module. >> >> PR libstdc++/110352 >> >> libstdc++-v3/ChangeLog: >> >> * include/std/mdspan (submdspan_extents): New function. >> * src/c++23/std.cc.in: Add submdspan_extents. >> * 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]> >> --- >> > LGTM. Thanks. > I have double checked and all functions call are qualified in this patch. > libstdc++-v3/include/std/mdspan | 103 +++++++++++ >> libstdc++-v3/src/c++23/std.cc.in | 5 +- >> .../testsuite/23_containers/mdspan/int_like.h | 9 + >> .../mdspan/submdspan/submdspan_extents.cc | 169 ++++++++++++++++++ >> .../mdspan/submdspan/submdspan_extents_neg.cc | 48 +++++ >> 5 files changed, 333 insertions(+), 1 deletion(-) >> 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 4fce6172589..31395b068b9 100644 >> --- a/libstdc++-v3/include/std/mdspan >> +++ b/libstdc++-v3/include/std/mdspan >> @@ -865,6 +865,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> return extents<_IndexType, _Extents::static_extent(_Index)>{ >> __exts.extent(_Index)}; >> } >> + >> + template<typename _IndexType, typename... _Slices> >> + consteval auto >> + __subrank() >> + { >> + return (static_cast<size_t>(!convertible_to<_Slices, _IndexType>) >> + + ... + 0); >> + } >> + >> + template<typename _IndexType, typename... _Slices> >> + consteval auto >> + __inv_map_rank() >> + { >> + constexpr auto __rank = sizeof...(_Slices); >> + constexpr auto __sub_rank = __subrank<_IndexType, _Slices...>(); >> + auto __map = std::array<size_t, __sub_rank>{}; >> + auto __is_int_like = std::array<bool, __rank>{ >> + convertible_to<_Slices, _IndexType>...}; >> + >> + size_t __i = 0; >> + for (size_t __k = 0; __k < __rank; ++__k) >> + if (!__is_int_like[__k]) >> + __map[__i++] = __k; >> + return __map; >> + } >> #endif // __glibcxx_submdspan >> } >> >> @@ -2659,8 +2684,86 @@ _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<size_t _K, typename _Extents, typename _Slice> >> + constexpr typename _Extents::index_type >> + __dynamic_slice_extent(const _Extents& __exts, _Slice __slice) >> + { >> + if constexpr (__is_strided_slice<_Slice>) >> + return __slice.extent == 0 ? 0 : 1 + (__slice.extent - 1) / >> __slice.stride; >> + else >> + return __exts.extent(_K); >> + } >> + >> + template<typename _IndexType, size_t... _Extents, typename... >> _Slices> >> + requires (sizeof...(_Slices) == sizeof...(_Extents)) >> + constexpr auto >> + __subextents(const extents<_IndexType, _Extents...>& __exts, >> + _Slices... __slices) >> + { >> + constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType, >> _Slices...>(); >> + auto __impl = [&]<size_t... _Indices>(index_sequence<_Indices...>) >> + { >> + using _SubExtents = extents<_IndexType, >> + (__mdspan::__static_slice_extent<_IndexType, >> + _Extents...[__inv_map[_Indices]], >> + _Slices...[__inv_map[_Indices]]>())...>; >> + if constexpr (_SubExtents::rank_dynamic() == 0) >> + return _SubExtents{}; >> + else >> + { >> + using _StaticSubExtents = __mdspan::_StaticExtents< >> + __mdspan::__static_extents<_SubExtents>()>; >> + auto __create = [&]<size_t... _Is>(index_sequence<_Is...>) >> + { >> + constexpr auto __slice_idx = [__inv_map](size_t __i) >> consteval >> + { >> + return >> __inv_map[_StaticSubExtents::_S_dynamic_index_inv(__i)]; >> + }; >> + >> + return _SubExtents{ >> + (__mdspan::__dynamic_slice_extent<__slice_idx(_Is)>( >> + __exts, __slices...[__slice_idx(_Is)]))...}; >> + }; >> + constexpr auto __dyn_subrank = _SubExtents::rank_dynamic(); >> + return __create(make_index_sequence<__dyn_subrank>()); >> + } >> + }; >> + >> + return __impl(make_index_sequence<__inv_map.size()>()); >> + } >> } >> >> + template<typename _IndexType, size_t... _Extents, typename... >> _RawSlices> >> + requires (sizeof...(_RawSlices) == sizeof...(_Extents)) >> + constexpr auto >> + submdspan_extents(const extents<_IndexType, _Extents...>& __exts, >> + _RawSlices... __raw_slices) >> + { >> + auto __impl = [&__exts](auto... __slices) >> + { >> + __mdspan::__check_valid_slices(__exts, __slices...); >> + return __mdspan::__subextents(__exts, __slices...); >> + }; >> + return __impl(__mdspan::__slice_cast<_IndexType>(__raw_slices)...); >> + } >> + >> template<typename _IndexType, size_t... _Extents, typename... >> _RawSlices> >> requires (sizeof...(_Extents) == sizeof...(_RawSlices)) >> constexpr auto >> diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/ >> std.cc.in >> index 63b3d183bf5..c2a9293b05a 100644 >> --- a/libstdc++-v3/src/c++23/std.cc.in >> +++ b/libstdc++-v3/src/c++23/std.cc.in >> @@ -1877,13 +1877,16 @@ export namespace std >> #if __glibcxx_padded_layouts >> using std::layout_left_padded; >> using std::layout_right_padded; >> +#endif >> +#if __glibcxx_submdspan >> using std::strided_slice; >> using std::full_extent_t; >> using std::full_extent; >> using std::submdspan_mapping_result; >> using std::submdspan_canonicalize_slices; >> + using std::submdspan_extents; >> #endif >> - // FIXME submdspan_extents, mdsubspan >> + // FIXME mdsubspan >> } >> #endif >> >> diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h >> b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h >> index 2f79b9dd3b5..b839be73ae9 100644 >> --- a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h >> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h >> @@ -69,4 +69,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..841910a77c8 >> --- /dev/null >> +++ >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc >> @@ -0,0 +1,169 @@ >> +// { dg-do run { target c++26 } } >> +#include <mdspan> >> + >> +#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<typename, typename> typename Pair, template<int> >> typename Cw> >> + constexpr bool >> + test_from_tuple() >> + { >> + auto exts = std::extents<int, 3, 5, 7>{}; >> + auto s0 = Cw<1>{}; >> + auto s1 = Pair{Cw<1>{}, Cw<2>{}}; >> + auto s2 = Pair{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<template<int> typename Cw> >> + constexpr bool >> + test_from_tuple_all() >> + { >> + test_from_tuple<std::tuple, Cw>(); >> + test_from_tuple<std::pair, Cw>(); >> + return true; >> + } >> + >> + >> +template<typename Int> >> + void >> + 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); >> + } >> + >> +template<template<int> typename Cw> >> + constexpr bool >> + test_from_const_int() >> + { >> + auto exts = std::extents<int, 3, 5>{}; >> + auto sub_exts = submdspan_extents(exts, Cw<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_all<CW>(); >> + test_from_tuple_all<IC>(); >> + test_from_const_int<CW>(); >> + test_from_const_int<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<CustomIndexType<CustomIndexKind::Const, >> true>>(); >> + >> test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::Throwing, >> true>>(); >> + >> test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::Mutating, >> true>>(); >> + test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::RValue, >> true>>(); >> + 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..cf27c0c7e4d >> --- /dev/null >> +++ >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc >> @@ -0,0 +1,48 @@ >> +// { dg-do compile { target c++26 } } >> +#include <mdspan> >> + >> +#include <cstdint> >> + >> +struct NotASlice >> +{ }; >> + >> +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_invalid_stride_zero() >> +{ >> + auto exts = std::extents(3, 5, 7); >> + auto s = std::strided_slice{0, 1, 0}; >> + auto sub_exts = submdspan_extents(exts, 1, s, 2); // { dg-error >> "expansion of" } >> + return true; >> +} >> +static_assert(test_invalid_stride_zero()); >> + >> +template<typename Slice> >> +constexpr bool >> +test_out_of_bounds(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(std::strided_slice{0, 6, 1})); // { >> dg-error "expansion of" } >> +static_assert(test_out_of_bounds(std::strided_slice{0, 7, 2})); // { >> dg-error "expansion of" } >> +static_assert(test_out_of_bounds(std::strided_slice{1, 6, 1})); // { >> dg-error "expansion of" } >> +static_assert(test_out_of_bounds(std::strided_slice{1, 6, 2})); // { >> dg-error "expansion of" } >> +static_assert(test_out_of_bounds(std::tuple{1, 6})); // { >> dg-error "expansion of" } >> +static_assert(test_out_of_bounds(std::tuple{std::cw<1>, std::cw<6>})); >> // { dg-error "expansion of" } >> +static_assert(test_out_of_bounds(std::strided_slice{-1, 2, 1})); // { >> dg-error "expansion of" } >> +static_assert(test_out_of_bounds(std::tuple{-1, 2})); // { >> dg-error "expansion of" } >> + >> +// { dg-prune-output "cannot decompose class type 'NotASlice'" } >> +// { dg-prune-output "static assertion failed" } >> +// { dg-prune-output "__glibcxx_assert_fail" } >> +// { dg-prune-output "non-constant condition" } >> -- >> 2.52.0 >> >>
