Regarding, the "satsifies" and "models", the concept has two kinds of
requirements:
* syntactic ones, this is usually what goes into the requires expression
inside the concept,
  but in general means this will be checked by compiler when checking a
concept,
  for example of equivalence_relation (
https://eel.is/c++draft/concepts#concept.equiv)
  it just checks relation, i.e. operator being callable.
* semantics ones, this is constrain on the behavior of the operation, in
general they
  are not checked by the compiler, for example equivalence_relation (
https://eel.is/c++draft/concepts#concept.equiv-1)
  says that the functor needs to be an equivalence relation (transitive,
reflective, e.t.c.)

Now, when we say that concept is satisfied, we mean that the compile time
only checks
passed. When we say that concept is modeled it means that both syntatic and
semantic
check passed. But only satisfaction can really be checked by compile time,
so the proper
way to expressing the requirements on function would be to say:
Constrains: T satisfies Concept
Precondition: T models Concept

However, we found that repetition in the wording tedious, and we have
generic wording
here: https://eel.is/c++draft/res.on.requirements#2, that allows us to
write:
  Constrains/Mandates: T models Concept
This means Constrain/Mandate on satisfaction and Precondition on Modeling.
[ Note:
The later is a bi more subtle, because if you do not model concept we say
IF-NDR,
the intent here was to allow compile to detect some thing from "models"
part at compile
time, but it also have wide ranging effect. ]

So, when we say that sliceable-concept is satisfied if
submdpsan_mapping(map, fe....)
[fe is full_extent_t],  is well formed, and modeled if if meet layout
mappings requirements, that means
when we say on submdspan:
  Constraints: LayoutPolicy::mapping<Extents> models sliceable-mapping.

Then we will put in requires the expression that check with pack of
full_extent, and
this function will not participate in overload resolution. Later we assume
that if that
worked, then your mapping will be callable will all valid slice types for
your mapping,
and if that is not the case is on you. [ Note: We say only valid, so you do
not need to
check preconditions, e.t.c if we give you something out of bounds, then
this is on us. ]


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

Reply via email to