On Thu, Nov 20, 2025 at 11:00 PM Tomasz Kaminski <[email protected]> wrote:
> > > On Thu, Nov 20, 2025 at 8:03 PM Luc Grosheintz <[email protected]> > wrote: > >> >> >> On 11/20/25 15:26, Tomasz Kaminski wrote: >> > On Tue, Nov 18, 2025 at 3:32 PM Luc Grosheintz < >> [email protected]> >> > wrote: >> > >> >> Implements submdspan_canonicalize_slices as described in P3663 and adds >> >> it to the std module. >> >> >> >> PR libstdc++/110352 >> >> >> >> libstdc++-v3/ChangeLog: >> >> >> >> * include/std/mdspan (submdspan_canonicalize_slices): New >> >> function. >> >> * src/c++23/std.cc.in (submdspan_canonicalize_slices): Add. >> >> * >> >> >> testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices.cc: >> >> New test. >> >> * >> >> >> testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices_neg.cc: >> >> New test. >> >> >> >> Signed-off-by: Luc Grosheintz <[email protected]> >> >> --- >> >> >> > The implementation looks good, but has a lot of type checks are not >> needed: >> > __valid_XXX concepts should not look into the types, as >> > canonicalization guarantees that the >> > argument has such types, so I would remove them and inline the few >0 >> > checks into the corresponding >> > __assert_valid_index, and also rename the functions to just >> __valid_index. >> > The standard specifies the requirements, because we need to describe >> what >> > needs to be accepted by >> > user defined mappings, and we just reuse them from Mandates, knowing >> that >> > they describe more, that >> > need to be checked. The assumption is that implementation will skip the >> > ones that are already guaranteed. >> >> The reason we have them is because there's a user-facing function >> that accepts only canonicalized slices: >> >> submdspan_mapping >> >> I think this way things will break nicer when a user passes in non- >> canonical collapsing index types, e.g. uint8_t or integral_constant. >> Personally, I like the "safety net" it provides to users. >> > I do not expect user to use this interface directly, this is an > ADL-customization > point that needs to be also provided for standardized mapping. Also calling > it correctly (via ADL only) is not that simple. We for example do not want > to redo all checks for preconditions in this functions, despite them being > exposed. > > If we have uses for creating submapping only, we should create a > new function std::submapping, that will perform cannonicalization, > ADL-poisoing and call submdspan_mapping. That would be proper > "user-facing" function. > > >> In fact, I read the standard as requiring that for non-canonical slices >> calling submdspan_mapping should be ill-formed. The reasoning is: >> >> 1. The mapping passed to submdspan models sliceable-mapping. >> 2. "LayoutMapping models sliceable-mapping" is defined as "LayoutMapping >> meets the sliceable layout mapping requirements". >> 3. A type M meets the sliceable layout mapping requirements if, >> - the expression submdspan_mapping(m, invalid_slices...) is ill-formed >> > invalid_slices are not not-cannonical slices: > > - > > (1.6) invalid_slices denote a pack of objects for which > sizeof...(invalid_slices) > == M_rank is true and there exists an integer *k* such that the > cv-unqualified type of invalid_slices...[*k*] is none of the following: > - > > (1.6.1) IT, > - > > (1.6.2) full_extent_t, > - > > (1.6.3) a specialization of constant_wrapper, or > - > > (1.6.3) a specialization of strided_slice. > The required checks are much less strict. It is correct to > constrain > - with valid slice_typoe. So would be something like this > > > template<typename IndexType, typename T> > concept acceptable_slice = > is_same_v<IndexType, T> > || is_same_v<T, full_extent> > || is_constant_wrapper<T> > || is_strided_slice<T>; > > > - ... >> >> Admittedly, this is slightly odd because we have both: >> >> - A type LayoutMapping satisfies sliceable-mapping >> - A type LayoutMapping models sliceable-mapping >> >> and the two mean different things. I don't know why we need the >> satisfies version. >> > The statisfies, means what is put into concept, so when we have sliceable > mapping concept it would check only what is in statisfies, this needs to > go on > the required clause of the sumbs span (i.e. we check only with full_extent > pack). I will give you more info on how exactly the check looks. > > The models part is things that are not checked when using the concept on > submdspan, i.e. if you break them it's UB. > >> >> AFAICT, we're only allowed to pass a standardized mapping to >> submdspan if it models sliceable-mapping. > > Yes, the library or someone using sliceable-apping is not allowed to pass > invalid (including runtime checks) to submdspan_mapping. And if they do > (outside of invalid-slices), the submdspan-mapping may be UB. > > >> Hence, we must write >> submdspan_mapping in such a way that it rejects non-canonical >> slices. > > This way submdspan_mapping is not required to do the checks (especially > for user-defined mappins), the only checks that are required are one that > allows > introduction of new slices types, i.e. invalid slices. > > - > > > >> We do it like this: >> >> template<__mdspan::__valid_canonical_slice_type<index_type>... _Slices> >> > This could be something like as far as stanard is concerned. > __mdspan::__acceptable_slice<index_type> _Slices > Or, more preferably, this should be static_assert( __mdspan::__acceptable_slice<index_type, Slices> && ... ), inside the body. The standard requires calls to be ill-formed for invalid_indicies. > requires (...) >> friend constexpr auto >> submdspan_mapping(const mapping& __mapping, _Slices... __slices); >> >> Does this make any sense? >> > Yes, but the whole canonicalization thing goal was to reduce reduce the > compile time, > and redoing full checks for direct-call to submdspan_mapping, that > introduce additional > compile time cost on path via submdspan. > Broke the sentence in the middle, I believe adding additional compile time cost on "normal" path for direct ues of customization points, defies the point of the change. > >> > >> > Also, the P3663 paper added support to use any destructruturable type >> (auto >> > [a, b] = slice) as slice type, >> > so we should test for aggregates. So we should test for structures that >> do >> > not have members. I have added >> > some suggestions below. >> > >> > I would also add a single test (not covering all cases) for array<int, >> 2>. >> > >> > >> > libstdc++-v3/include/std/mdspan | 199 ++++++++++++++++ >> >> libstdc++-v3/src/c++23/std.cc.in | 1 + >> >> .../submdspan_canonicalize_slices.cc | 212 >> ++++++++++++++++++ >> >> .../submdspan_canonicalize_slices_neg.cc | 208 +++++++++++++++++ >> >> 4 files changed, 620 insertions(+) >> >> create mode 100644 >> >> >> libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices.cc >> >> create mode 100644 >> >> >> libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices_neg.cc >> >> >> >> diff --git a/libstdc++-v3/include/std/mdspan >> >> b/libstdc++-v3/include/std/mdspan >> >> index 0a49f24fd3f..70656bcdd01 100644 >> >> --- a/libstdc++-v3/include/std/mdspan >> >> +++ b/libstdc++-v3/include/std/mdspan >> >> @@ -33,20 +33,21 @@ >> >> #pragma GCC system_header >> >> #endif >> >> >> >> #include <span> >> >> #include <array> >> >> #include <type_traits> >> >> #include <utility> >> >> >> >> #if __cplusplus > 202302L >> > Include `bit/version.h` before the headers, >> > and use __glibcxx_submdspan to guard including a tuple. >> > >> > >> >> #include <bits/align.h> >> >> +#include <tuple> >> >> #endif >> >> >> >> #define __glibcxx_want_mdspan >> >> #define __glibcxx_want_aligned_accessor >> >> #define __glibcxx_want_submdspan >> >> #include <bits/version.h> >> >> >> >> #ifdef __glibcxx_mdspan >> >> >> >> namespace std _GLIBCXX_VISIBILITY(default) >> >> @@ -819,20 +820,204 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> >> return 0; >> >> else if (__empty(__m.extents())) >> >> return 0; >> >> else >> >> { >> >> auto __impl = [&__m]<size_t... >> >> _Counts>(index_sequence<_Counts...>) >> >> { return __m(((void) _Counts, _IndexType(0))...); }; >> >> return __impl(make_index_sequence<__rank>()); >> >> } >> >> } >> >> + >> >> +#ifdef __glibcxx_submdspan >> >> >> > I would preffer if would put this just before >> submdspan_canonical_slices, >> > in single >> > if def block. I.e. reopen namespace twice. >> > >> >> + template<typename _Tp> >> >> + inline constexpr bool __is_strided_slice = false; >> >> >> > Here and below, constexpr variable are automatically inline. >> > >> >> + >> >> + template<typename _OffsetType, typename _ExtentType, typename >> >> _StrideType> >> >> + inline constexpr bool >> __is_strided_slice<strided_slice<_OffsetType, >> >> + _ExtentType, _StrideType>> = true; >> >> + >> >> + template<typename _IndexType, typename _OIndexType> >> >> + consteval bool >> >> + __is_representable_integer(_OIndexType __value) >> >> + { >> >> >> > This is consteval function, so it always evalauted at compile time, so >> we >> > do not need >> > to do if constexpr check, so I think we can just say: >> > return !std::cmp_less(__value, __min) && !std::cmp_greater(__value, >> > __ma); >> > >> > >> >> + constexpr auto __min = >> __gnu_cxx::__int_traits<_IndexType>::__min; >> >> + constexpr auto __omin = >> >> __gnu_cxx::__int_traits<_OIndexType>::__min; >> >> + constexpr auto __max = >> __gnu_cxx::__int_traits<_IndexType>::__max; >> >> + constexpr auto __omax = >> >> __gnu_cxx::__int_traits<_OIndexType>::__max; >> >> + >> >> + if constexpr (std::cmp_less(__omin, __min)) >> >> + if(std::cmp_less(__value, __min)) >> >> + return false; >> >> + if constexpr (std::cmp_greater(__omax, __max)) >> >> + if(std::cmp_greater(__value, __max)) >> >> + return false; >> >> + return true; >> >> + } >> >> + >> >> + template<typename _IndexType, typename _Slice> >> >> + constexpr auto >> >> + __canonical_index(_Slice&& __slice) >> >> + { >> >> + if constexpr (__detail::__integral_constant_like<_Slice>) >> >> + { >> >> + >> >> static_assert(__is_representable_integer<_IndexType>(_Slice::value)); >> >> + static_assert(std::cmp_greater_equal(_Slice::value, 0)); >> >> + return std::cw<_IndexType(_Slice::value)>; >> >> + } >> >> + else >> >> + return __index_type_cast<_IndexType>(std::move(__slice)); >> >> + } >> >> + >> >> + template<typename _Tp> >> >> + inline constexpr bool __is_constant_wrapper = false; >> >> + >> >> + template<_CwFixedValue _Xv, typename _Tp> >> >> + inline constexpr bool >> __is_constant_wrapper<constant_wrapper<_Xv, >> >> _Tp>> >> >> + = true; >> >> + >> >> + template<typename _Slice, typename _IndexType> >> >> + concept __valid_canonical_index_type = same_as<_Slice, >> _IndexType> >> >> + || (__is_constant_wrapper<_Slice> >> >> + && same_as<typename _Slice::value_type, _IndexType> >> >> + && (_Slice::value >= 0)); >> >> + >> >> + template<typename _Slice, typename _IndexType> >> >> + concept __valid_strided_slice_type = __is_strided_slice<_Slice> >> >> + && __valid_canonical_index_type<typename _Slice::offset_type, >> >> _IndexType> >> >> + && __valid_canonical_index_type<typename _Slice::extent_type, >> >> _IndexType> >> >> + && __valid_canonical_index_type<typename _Slice::stride_type, >> >> _IndexType> >> >> + && (!(__is_constant_wrapper<typename _Slice::extent_type> >> >> + && __is_constant_wrapper<typename _Slice::stride_type>) >> >> + || (_Slice::stride_type::value > 0)); >> >> + >> >> + template<typename _Slice, typename _IndexType> >> >> + concept __valid_canonical_slice_type = same_as<_Slice, >> _IndexType> >> >> + || same_as<_Slice, full_extent_t> >> >> + || __valid_strided_slice_type<_Slice, _IndexType> >> >> + || __valid_canonical_index_type<_Slice, _IndexType>; >> >> >> > Because the slices are canonicalized, we do not need to validate the >> types, >> > (we know exactly what was produced), so I think we can simply remove >> this >> > concept, >> > and move relevant checks to corresponding __valid functions. >> > >> >> + >> >> + template<typename _IndexType, typename _Slice> >> >> + constexpr auto >> >> + __slice_cast(_Slice&& __slice) >> >> + { >> >> + using _SliceType = remove_cvref_t<_Slice>; >> >> + if constexpr (is_convertible_v<_SliceType, full_extent_t>) >> >> + return static_cast<full_extent_t>(std::move(__slice)); >> >> + else if constexpr (is_convertible_v<_SliceType, _IndexType>) >> >> + return __canonical_index<_IndexType>(std::move(__slice)); >> >> + else if constexpr (__is_strided_slice<_SliceType>) >> >> + { >> >> + auto __extent = __canonical_index<_IndexType>( >> >> + std::move(__slice.extent)); >> >> + auto __offset = __canonical_index<_IndexType>( >> >> + std::move(__slice.offset)); >> >> + if constexpr (is_same_v<decltype(__extent), >> >> + constant_wrapper<_IndexType(0)>>) >> >> + return strided_slice{ >> >> + .offset = __offset, >> >> + .extent = __extent, >> >> + .stride = cw<_IndexType(1)> >> >> + }; >> >> + else >> >> + return strided_slice{ >> >> + .offset = __offset, >> >> + .extent = __extent, >> >> + .stride = __canonical_index<_IndexType>( >> >> + std::move(__slice.stride)) >> >> + }; >> >> + } >> >> + else >> >> + { >> >> + auto [__sbegin, __send] = std::move(__slice); >> >> + auto __offset = >> >> __canonical_index<_IndexType>(std::move(__sbegin)); >> >> + auto __end = >> __canonical_index<_IndexType>(std::move(__send)); >> >> + return strided_slice{ >> >> + .offset = __offset, >> >> + .extent = __canonical_index<_IndexType>(__end - >> __offset), >> >> + .stride = cw<_IndexType(1)> >> >> + }; >> >> + } >> >> + } >> >> + >> >> + template<typename _IndexType, size_t _Extent, typename >> _OIndexType> >> >> + constexpr void >> >> + __assert_valid_index(const extents<_IndexType, _Extent>& __ext, >> >> + const _OIndexType& __idx) >> >> >> > + { >> >> >> > >> > >> >> + if constexpr (__is_constant_wrapper<_OIndexType> >> >> + && _Extent != dynamic_extent) >> >> >> > And the static_assert(_OIndexType::value) would be hre. >> > >> >> + static_assert(std::cmp_less_equal(_OIndexType::value, >> >> _Extent)); >> >> >> > We know that __value is of type _IndexType so we can just use <. >> > >> >> + else >> >> + __glibcxx_assert(std::cmp_less_equal( >> >> + static_cast<_IndexType>(__idx), __ext.extent(0))); >> >> >> > Same here. >> > >> >> + } >> >> + >> >> + template<typename _IndexType, size_t _Extent, typename _Slice> >> >> + constexpr void >> >> + __assert_valid_slice(const extents<_IndexType, _Extent>& __ext, >> >> + const _Slice& __slice) >> >> + { >> >> + static_assert(__valid_canonical_slice_type<_Slice, >> _IndexType>); >> >> The above looks pointless, but canonicalization doesn't produce valid >> slice types, e.g. it's possible for a constant_wrapper to be out-of-range. >> >> Therefore, to avoid having two concepts (and needing names for both), it >> uses __valid_canonical_slice_type. >> >> >> + >> >> + if constexpr (__is_strided_slice<_Slice>) >> >> + { >> >> + if constexpr (!(__is_constant_wrapper<typename >> >> _Slice::extent_type> >> >> + && __is_constant_wrapper<typename >> >> _Slice::stride_type>)) >> >> + __glibcxx_assert(__slice.extent == 0 || __slice.stride > >> 0); >> >> >> > Similar places static assert for concepts here. >> > >> >> + >> >> + // DEVIATION: For empty slices, P3663r3 does not allow us >> to >> >> check >> >> + // that this is less than or equal to the k-th extent (at >> >> runtime). >> >> + // We're only allowed to check if __slice.offset, >> >> __slice.extent >> >> + // are constant wrappers and __ext is a static extent. >> >> + __assert_valid_index(__ext, __slice.offset); >> >> + __assert_valid_index(__ext, __slice.extent); >> >> >> > I would put them before looking into stride, so at beginning. >> > >> >> + >> >> + if constexpr (__is_constant_wrapper<typename >> >> _Slice::offset_type> >> >> + && __is_constant_wrapper<typename _Slice::extent_type> >> >> + && _Extent != dynamic_extent) >> >> + static_assert(std::cmp_greater_equal( >> >> + _Extent - _Slice::offset_type::value, >> >> + _Slice::extent_type::value)); >> >> + else >> >> + __glibcxx_assert(__ext.extent(0) - __slice.offset >> >> + >= __slice.extent); >> >> + } >> >> >> > The last else constexpr could be replaced with: >> > if constexpr (!is_samve_v<_Slice, _FullExtents>) >> > __assert_valid_index(__ext, __slice.offset); >> > If it is not strided slice, or full_extent, it must be single index. >> > >> >> + else if constexpr (__is_constant_wrapper<_Slice> >> >> + && _Extent != dynamic_extent) >> >> + static_assert(std::cmp_less(_Slice::value, _Extent)); >> >> + else if constexpr (convertible_to<_Slice, _IndexType>) >> >> + __glibcxx_assert(__slice < __ext.extent(0)); >> >> + } >> >> + >> >> + template<size_t _Index, typename _Extents> >> >> + constexpr auto >> >> + __extract_extent(const _Extents& __exts) >> >> + { >> >> + using _IndexType = typename _Extents::index_type; >> >> + return extents<_IndexType, _Extents::static_extent(_Index)>{ >> >> + __exts.extent(_Index)}; >> >> + } >> >> + >> >> + template<typename _Extents, typename... _Slices> >> >> + constexpr void >> >> + __assert_valid_slices(const _Extents& __exts, const _Slices&... >> >> __slices) >> >> + { >> >> + constexpr auto __rank = _Extents::rank(); >> >> + auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>) >> >> + { >> >> + ((__assert_valid_slice(__extract_extent<_Is>(__exts), >> >> + __slices...[_Is])),...); >> >> + }; >> >> + __impl(make_index_sequence<__rank>()); >> >> + } >> >> +#endif // __glibcxx_submdspan >> >> } >> >> >> >> template<typename _Extents> >> >> class layout_left::mapping >> >> { >> >> public: >> >> using extents_type = _Extents; >> >> using index_type = typename extents_type::index_type; >> >> using size_type = typename extents_type::size_type; >> >> using rank_type = typename extents_type::rank_type; >> >> @@ -2492,14 +2677,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> >> -> mdspan<_ElementType, typename _MappingType::extents_type, >> >> typename _MappingType::layout_type>; >> >> >> >> template<typename _MappingType, typename _AccessorType> >> >> mdspan(const typename _AccessorType::data_handle_type&, const >> >> _MappingType&, >> >> const _AccessorType&) >> >> -> mdspan<typename _AccessorType::element_type, >> >> typename _MappingType::extents_type, >> >> typename _MappingType::layout_type, _AccessorType>; >> >> >> >> +#if __glibcxx_submdspan >> >> + template<typename _IndexType, size_t... _Extents, typename... >> _Slices> >> >> + 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...); >> >> + } >> >> +#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 dd458a230e6..63b3d183bf5 100644 >> >> --- a/libstdc++-v3/src/c++23/std.cc.in >> >> +++ b/libstdc++-v3/src/c++23/std.cc.in >> >> @@ -1874,20 +1874,21 @@ export namespace std >> >> using std::aligned_accessor; >> >> #endif >> >> using std::mdspan; >> >> #if __glibcxx_padded_layouts >> >> using std::layout_left_padded; >> >> using std::layout_right_padded; >> >> using std::strided_slice; >> >> using std::full_extent_t; >> >> using std::full_extent; >> >> using std::submdspan_mapping_result; >> >> + using std::submdspan_canonicalize_slices; >> >> #endif >> >> // FIXME submdspan_extents, mdsubspan >> >> } >> >> #endif >> >> >> >> // 20.2 <memory> >> >> export namespace std >> >> { >> >> using std::align; >> >> using std::allocator; >> >> diff --git >> >> >> a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices.cc >> >> >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices.cc >> >> new file mode 100644 >> >> index 00000000000..1dec3548c1d >> >> --- /dev/null >> >> +++ >> >> >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices.cc >> >> @@ -0,0 +1,212 @@ >> >> +// { dg-do run { target c++26 } } >> >> +#include <mdspan> >> >> + >> >> +#include <testsuite_hooks.h> >> >> +#include <cstddef> >> >> +#include <cstdint> >> >> + >> >> +constexpr size_t dyn = std::dynamic_extent; >> >> + >> >> +template<typename Extents, typename CInt> >> >> + constexpr bool >> >> + check_collapsing(Extents exts, CInt ci_raw) >> >> + { >> >> + using IndexType = typename Extents::index_type; >> >> + auto ci_expected = std::cw<IndexType{ci_raw.value}>; >> >> + auto [ci] = std::submdspan_canonicalize_slices(exts, ci_raw); >> >> + static_assert(std::same_as<decltype(ci), decltype(ci_expected)>); >> >> + VERIFY(std::cmp_equal(ci.value, ci_raw.value)); >> >> + >> >> + auto [i] = std::submdspan_canonicalize_slices(exts, ci_raw.value); >> >> + static_assert(std::same_as<decltype(i), IndexType>); >> >> + VERIFY(std::cmp_equal(i, ci_raw.value)); >> >> + return true; >> >> + } >> >> + >> >> +template<typename Extents> >> >> + constexpr bool >> >> + test_scalar(Extents exts) >> >> + { >> >> + using IndexType = typename Extents::index_type; >> >> + >> >> + check_collapsing(exts, std::cw<uint8_t{0}>); >> >> + check_collapsing(exts, std::cw<IndexType{0}>); >> >> + >> >> + check_collapsing(exts, std::cw<uint8_t{4}>); >> >> + check_collapsing(exts, std::cw<IndexType{4}>); >> >> + return true; >> >> + } >> >> + >> >> +constexpr bool >> >> +test_scalar() >> >> +{ >> >> + test_scalar(std::extents<int, dyn>{5}); >> >> + test_scalar(std::extents<int, 5>{}); >> >> + test_scalar(std::extents<unsigned int, dyn>{5}); >> >> + test_scalar(std::extents<unsigned int, 5>{}); >> >> + return true; >> >> +} >> >> + >> >> +constexpr void >> >> +assert_same(auto lhs, auto rhs) >> >> +{ >> >> + static_assert(std::same_as<decltype(lhs), decltype(rhs)>); >> >> + VERIFY(lhs == rhs); >> >> +} >> >> + >> >> +template<template<typename, typename> typename Pair> >> >> + constexpr bool >> >> + test_pair(auto exts, auto cbegin, auto cend, auto coffset, auto >> cextent) >> >> + { >> >> + using IndexType = typename decltype(exts)::index_type; >> >> + auto c1 = std::cw<IndexType{1}>; >> >> + >> >> + auto raw_cc = Pair{cbegin, cend}; >> >> + auto [cc] = std::submdspan_canonicalize_slices(exts, raw_cc); >> >> + assert_same(cc.offset, coffset); >> >> + assert_same(cc.extent, cextent); >> >> + assert_same(cc.stride, c1); >> >> + >> >> + auto raw_cd = Pair{cbegin, cend.value}; >> >> + auto [cd] = std::submdspan_canonicalize_slices(exts, raw_cd); >> >> + assert_same(cd.offset, coffset); >> >> + assert_same(cd.extent, cextent.value); >> >> + assert_same(cd.stride, c1); >> >> + >> >> + auto raw_dc = Pair{cbegin.value, cend}; >> >> + auto [dc] = std::submdspan_canonicalize_slices(exts, raw_dc); >> >> + assert_same(dc.offset, coffset.value); >> >> + assert_same(dc.extent, cextent.value); >> >> + assert_same(dc.stride, c1); >> >> + >> >> + auto raw_dd = Pair{cbegin.value, cend.value}; >> >> + auto [dd] = std::submdspan_canonicalize_slices(exts, raw_dd); >> >> + assert_same(dd.offset, coffset.value); >> >> + assert_same(dd.extent, cextent.value); >> >> + assert_same(dd.stride, c1); >> >> + return true; >> >> + } >> >> + >> >> +template<template<typename, typename> typename Pair> >> >> + constexpr bool >> >> + test_pair() >> >> + { >> >> + test_pair<Pair>(std::extents<int, dyn>{5}, std::cw<uint8_t{2}>, >> >> + std::cw<uint8_t{5}>, std::cw<2>, std::cw<3>); >> >> + test_pair<Pair>(std::extents<int, 5>{}, std::cw<uint8_t{2}>, >> >> + std::cw<uint8_t{5}>, std::cw<2>, std::cw<3>); >> >> + test_pair<Pair>(std::extents<int, 0>{}, std::cw<uint8_t{0}>, >> >> + std::cw<uint8_t{0}>, std::cw<0>, std::cw<0>); >> >> + test_pair<Pair>(std::extents<int, dyn>{0}, std::cw<uint8_t{0}>, >> >> + std::cw<uint8_t{0}>, std::cw<0>, std::cw<0>); >> >> + return true; >> >> + } >> >> + >> >> +constexpr bool >> >> +test_pair_all() >> >> +{ >> >> + test_pair<std::pair>(); >> >> + test_pair<std::tuple>(); >> >> >> > The new code support also usual aggregates, so please add a test for >> > following: >> > template<typename T, typename U> >> > struct Aggregate { >> > T t; >> > U u; >> > }; >> > >> > It should work out of the box with the syntax you are using. >> > >> > >> >> + return true; >> >> +} >> >> + >> >> +constexpr bool >> >> +test_strided_slice(auto exts, auto co, auto ce, auto cs) >> >> +{ >> >> + using IndexType = decltype(exts)::index_type; >> >> + >> >> + auto coffset = std::cw<IndexType{co.value}>; >> >> + auto cextent = std::cw<IndexType{ce.value}>; >> >> + auto cstride = std::cw<IndexType{cs.value}>; >> >> + >> >> + auto raw_ccc = std::strided_slice{co, ce, cs}; >> >> + auto [ccc] = std::submdspan_canonicalize_slices(exts, raw_ccc); >> >> + assert_same(ccc.offset, coffset); >> >> + assert_same(ccc.extent, cextent); >> >> + assert_same(ccc.stride, cstride); >> >> + >> >> + auto raw_dcc = std::strided_slice{co.value, ce, cs}; >> >> + auto [dcc] = std::submdspan_canonicalize_slices(exts, raw_dcc); >> >> + assert_same(dcc.offset, coffset.value); >> >> + assert_same(dcc.extent, cextent); >> >> + assert_same(dcc.stride, cstride); >> >> + >> >> + auto raw_cdc = std::strided_slice{co, ce.value, cs}; >> >> + auto [cdc] = std::submdspan_canonicalize_slices(exts, raw_cdc); >> >> + assert_same(cdc.offset, coffset); >> >> + assert_same(cdc.extent, cextent.value); >> >> + assert_same(cdc.stride, cstride); >> >> + >> >> + auto raw_ccd = std::strided_slice{co, ce, cs.value}; >> >> + auto [ccd] = std::submdspan_canonicalize_slices(exts, raw_ccd); >> >> + assert_same(ccd.offset, coffset); >> >> + assert_same(ccd.extent, cextent); >> >> + assert_same(ccd.stride, cstride.value); >> >> + return true; >> >> +} >> >> + >> >> +constexpr bool >> >> +test_strided_slice() >> >> +{ >> >> + auto run = [](auto exts) >> >> + { >> >> + auto cs = std::cw<uint8_t{9}>; >> >> + test_strided_slice(exts, std::cw<uint8_t{2}>, std::cw<uint8_t{3}>, >> >> cs); >> >> + test_strided_slice(exts, std::cw<uint8_t{0}>, std::cw<uint8_t{5}>, >> >> cs); >> >> + }; >> >> + >> >> + run(std::extents<int, 5>{}); >> >> + run(std::extents<int, dyn>{5}); >> >> + return true; >> >> +} >> >> + >> >> +constexpr bool >> >> +test_strided_slice_zero_extent(auto exts, auto cs) >> >> +{ >> >> + using IndexType = typename decltype(exts)::index_type; >> >> + auto c0 = std::cw<uint8_t{0}>; >> >> + auto raw_ccc = std::strided_slice{c0, c0, cs}; >> >> + auto [ccc] = std::submdspan_canonicalize_slices(exts, raw_ccc); >> >> + assert_same(ccc.stride, std::cw<IndexType{1}>); >> >> + >> >> + auto raw_ccd = std::strided_slice{c0, c0, cs.value}; >> >> + auto [ccd] = std::submdspan_canonicalize_slices(exts, raw_ccd); >> >> + assert_same(ccd.stride, std::cw<IndexType{1}>); >> >> + return true; >> >> +} >> >> + >> >> +constexpr bool >> >> +test_strided_slice_zero_extent(auto exts) >> >> +{ >> >> + test_strided_slice_zero_extent(exts, std::cw<uint8_t{0}>); >> >> + test_strided_slice_zero_extent(exts, std::cw<uint8_t{9}>); >> >> + return true; >> >> +} >> >> + >> >> +constexpr bool >> >> +test_strided_slice_zero_extent() >> >> +{ >> >> + test_strided_slice_zero_extent(std::extents<int, 0>{}); >> >> + test_strided_slice_zero_extent(std::extents<int, dyn>{0}); >> >> + test_strided_slice_zero_extent(std::extents<int, 5>{}); >> >> + test_strided_slice_zero_extent(std::extents<int, dyn>{5}); >> >> + return true; >> >> +} >> >> + >> >> +constexpr bool >> >> +test_all() >> >> +{ >> >> + test_scalar(); >> >> + test_pair_all(); >> >> + test_strided_slice(); >> >> + test_strided_slice_zero_extent(); >> >> + return true; >> >> +} >> >> + >> >> +int >> >> +main() >> >> +{ >> >> + test_all(); >> >> + static_assert(test_all()); >> >> + return 0; >> >> +} >> >> diff --git >> >> >> a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices_neg.cc >> >> >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices_neg.cc >> >> new file mode 100644 >> >> index 00000000000..94bca183aa3 >> >> --- /dev/null >> >> +++ >> >> >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_canonicalize_slices_neg.cc >> >> @@ -0,0 +1,208 @@ >> >> +// { dg-do compile { target c++26 } } >> >> +#include <mdspan> >> >> + >> >> +#include <cstdint> >> >> + >> >> +constexpr size_t dyn = std::dynamic_extent; >> >> + >> >> +constexpr auto dyn_empty = std::extents<int32_t, dyn>{0}; >> >> +constexpr auto sta_empty = std::extents<uint32_t, 0>{}; >> >> + >> >> +constexpr auto dyn_uexts = std::extents<uint8_t, dyn>{5}; >> >> +constexpr auto sta_uexts = std::extents<uint16_t, 5>{5}; >> >> +constexpr auto dyn_sexts = std::extents<int8_t, dyn>{5}; >> >> +constexpr auto sta_sexts = std::extents<int16_t, 5>{5}; >> >> + >> >> +constexpr bool >> >> +test_rank_mismatch() >> >> +{ >> >> + auto exts = std::extents(1); >> >> + std::submdspan_canonicalize_slices(exts, 0, 0); // { dg-error "no >> >> matching" } >> >> + return true; >> >> +} >> >> + >> >> +template<typename Int, typename Extents> >> >> +constexpr bool >> >> +test_under1(Int i1, Extents exts) >> >> +{ >> >> + auto [s1] = std::submdspan_canonicalize_slices(exts, i1); >> >> + return true; >> >> +} >> >> + >> >> +static_assert(test_under1(-1, dyn_sexts)); // { dg-error "expansion >> of" >> >> } >> >> +static_assert(test_under1(-1, dyn_uexts)); // { dg-error "expansion >> of" >> >> } >> >> +static_assert(test_under1(-1, sta_sexts)); // { dg-error "expansion >> of" >> >> } >> >> +static_assert(test_under1(-1, sta_uexts)); // { dg-error "expansion >> of" >> >> } >> >> + >> >> +static_assert(test_under1(std::cw<-1>, dyn_sexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under1(std::cw<-1>, dyn_uexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under1(std::cw<-1>, sta_sexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under1(std::cw<-1>, sta_uexts)); // { dg-error >> >> "required from" } >> >> + >> >> +template<typename Int, typename Extents> >> >> +constexpr bool >> >> +test_over1(Int i1, Extents exts) >> >> +{ >> >> + auto [s1] = std::submdspan_canonicalize_slices(exts, i1); >> >> + return true; >> >> +} >> >> + >> >> +static_assert(test_over1(0, dyn_empty)); // { dg-error "expansion >> of" } >> >> +static_assert(test_over1(0, sta_empty)); // { dg-error "expansion >> of" } >> >> +static_assert(test_over1(5, dyn_sexts)); // { dg-error "expansion >> of" } >> >> +static_assert(test_over1(5, dyn_uexts)); // { dg-error "expansion >> of" } >> >> +static_assert(test_over1(5, sta_sexts)); // { dg-error "expansion >> of" } >> >> +static_assert(test_over1(5, sta_uexts)); // { dg-error "expansion >> of" } >> >> + >> >> +static_assert(test_over1(std::cw<0>, dyn_empty)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over1(std::cw<0>, sta_empty)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over1(std::cw<5>, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over1(std::cw<5>, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over1(std::cw<5>, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over1(std::cw<5>, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> + >> >> +template<typename Offset, typename Extent, typename Stride, typename >> >> Extents> >> >> + constexpr bool >> >> + test_under2(Offset o, Extent e, Stride s, Extents exts) >> >> + { >> >> + std::submdspan_canonicalize_slices(exts, std::strided_slice{o, e, >> s}); >> >> + return true; >> >> + } >> >> + >> >> +constexpr auto i8_1 = int8_t{1}; >> >> + >> >> +static_assert(test_under2(-i8_1, 0, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, -i8_1, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, 1, -i8_1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(-i8_1, 0, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, -i8_1, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, 1, -i8_1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(-i8_1, 0, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, -i8_1, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, 1, -i8_1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(-i8_1, 0, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, -i8_1, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_under2(0, 1, -i8_1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> + >> >> +constexpr auto c_i8_m1 = std::cw<int8_t{-1}>; >> >> +constexpr auto c_i16_m1 = std::cw<int16_t{-1}>; >> >> +constexpr auto c_i64_m1 = std::cw<int64_t{-1}>; >> >> + >> >> +static_assert(test_under2(c_i8_m1, 0, 1, dyn_uexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, c_i16_m1, 1, dyn_uexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, 1, c_i64_m1, dyn_uexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(c_i8_m1, 0, 1, dyn_sexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, c_i16_m1, 1, dyn_sexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, 1, c_i64_m1, dyn_sexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(c_i8_m1, 0, 1, sta_uexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, c_i16_m1, 1, sta_uexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, 1, c_i64_m1, sta_uexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(c_i8_m1, 0, 1, sta_sexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, c_i16_m1, 1, sta_sexts)); // { dg-error >> >> "required from" } >> >> +static_assert(test_under2(0, 1, c_i64_m1, sta_sexts)); // { dg-error >> >> "required from" } >> >> + >> >> +template<typename Offset, typename Extent, typename Stride, typename >> >> Extents> >> >> + constexpr bool >> >> + test_over2(Offset o, Extent e, Stride s, Extents exts) >> >> + { >> >> + std::submdspan_canonicalize_slices(exts, std::strided_slice{o, e, >> s}); >> >> + return true; >> >> + } >> >> + >> >> +constexpr auto i8_6 = int8_t{6}; >> >> +constexpr auto c_i8_6 = std::cw<int8_t{6}>; >> >> +constexpr auto c2 = std::cw<2>; >> >> +constexpr auto c4 = std::cw<4>; >> >> + >> >> +static_assert(test_over2(i8_6, 0, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, i8_6, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, 4, 0, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c_i8_6, 0, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, c_i8_6, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, 4, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, c4, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, c4, 1, dyn_uexts)); // { dg-error >> >> "expansion of" } >> >> + >> >> +static_assert(test_over2(i8_6, 0, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, i8_6, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, 4, 0, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c_i8_6, 0, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, c_i8_6, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, 4, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, c4, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, c4, 1, dyn_sexts)); // { dg-error >> >> "expansion of" } >> >> + >> >> +static_assert(test_over2(i8_6, 0, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, i8_6, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, 4, 0, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c_i8_6, 0, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, c_i8_6, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, 4, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, c4, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, c4, 1, sta_uexts)); // { dg-error >> >> "expansion of" } >> >> + >> >> +static_assert(test_over2(i8_6, 0, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, i8_6, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, 4, 0, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c_i8_6, 0, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(0, c_i8_6, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, 4, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(2, c4, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_over2(c2, c4, 1, sta_sexts)); // { dg-error >> >> "expansion of" } >> >> + >> >> +// Checks the precondition: offset + extent <= exts.extent(0) for >> unsigned >> >> +// index_type when offset + extent overflows. >> >> +constexpr bool >> >> +test_overflow1(auto o, auto e) >> >> +{ >> >> + auto exts = std::extents<uint8_t, dyn>{255}; >> >> + auto slice = std::strided_slice{o, e, 1}; >> >> + std::submdspan_canonicalize_slices(exts, slice); >> >> + return true; >> >> +} >> >> + >> >> +static_assert(test_overflow1(128, 128)); // { >> dg-error >> >> "expansion of" } >> >> +static_assert(test_overflow1(std::cw<128>, 128)); // { >> dg-error >> >> "expansion of" } >> >> +static_assert(test_overflow1(128, std::cw<128>)); // { >> dg-error >> >> "expansion of" } >> >> +static_assert(test_overflow1(std::cw<128>, std::cw<128>)); // { >> dg-error >> >> "expansion of" } >> >> + >> >> +constexpr bool >> >> +test_overflow2(auto b, auto e) >> >> +{ >> >> + auto exts = std::extents<uint8_t, dyn>{255}; >> >> + auto slice = std::pair{b, e}; >> >> + std::submdspan_canonicalize_slices(exts, slice); >> >> + return true; >> >> +} >> >> + >> >> +static_assert(test_overflow2(5, 4)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_overflow2(std::cw<5>, 4)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_overflow2(5, std::cw<4>)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_overflow2(std::cw<5>, std::cw<4>)); // { dg-error >> >> "expansion of" } >> >> + >> >> +constexpr auto u8_4 = uint8_t{4}; >> >> +constexpr auto u8_5 = uint8_t{5}; >> >> +static_assert(test_overflow2(u8_5, u8_4)); // { >> >> dg-error "expansion of" } >> >> +static_assert(test_overflow2(std::cw<u8_5>, u8_4)); // { >> >> dg-error "expansion of" } >> >> +static_assert(test_overflow2(u8_5, std::cw<u8_4>)); // { >> >> dg-error "expansion of" } >> >> +static_assert(test_overflow2(std::cw<u8_5>, std::cw<u8_4>)); // { >> >> dg-error "expansion of" } >> >> + >> >> +constexpr bool >> >> +test_invalid(auto e, auto s) >> >> +{ >> >> + auto exts = std::extents(5); >> >> + auto slice = std::strided_slice(0, e, s); >> >> + std::submdspan_canonicalize_slices(exts, slice); >> >> + return true; >> >> +} >> >> + >> >> +static_assert(test_invalid(3, 0)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_invalid(3, std::cw<0>)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_invalid(3, std::cw<0>)); // { dg-error >> >> "expansion of" } >> >> +static_assert(test_invalid(std::cw<3>, std::cw<0>)); // { dg-error >> >> "expansion of" } >> >> + >> >> + >> >> +// { dg-prune-output "static assertion failed" } >> >> +// { dg-prune-output "__glibcxx_assert_fail" } >> >> +// { dg-prune-output "__glibcxx_assert" } >> >> +// { dg-prune-output "non-constant condition" } >> >> -- >> >> 2.51.2 >> >> >> >> >> > >> >>
