[PATCH v2 02/10] libstdc++: Implement submdspan_canonicalize_slices. [PR110352]

Luc Grosheintz luc.grosheintz@gmail.com
Thu Nov 20 19:05:49 GMT 2025



On 11/20/25 15:26, Tomasz Kaminski wrote:
> On Tue, Nov 18, 2025 at 3:32 PM Luc Grosheintz <luc.grosheintz@gmail.com>
> 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 <luc.grosheintz@gmail.com>
>> ---
>>
> 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.

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
    - ...

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.

AFAICT, we're only allowed to pass a standardized mapping to
submdspan if it models sliceable-mapping. Hence, we must write
submdspan_mapping in such a way that it rejects non-canonical
slices. We do it like this:

   template<__mdspan::__valid_canonical_slice_type<index_type>... _Slices>
     requires (...)
     friend constexpr auto
     submdspan_mapping(const mapping& __mapping, _Slices... __slices);

Does this make any sense?

> 
> 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
>>
>>
> 



More information about the Libstdc++ mailing list