*Slowly* getting clearer.

On 11/21/25 15:58, Luc Grosheintz wrote:
Thank you many things are (quickly) getting clearer.

For submdspan_mapping, we only apply the contraints needed
to satisfy sliceable-mapping. This ensures that users are
allowed to pass standardized mappings to submdspan. We don't
add any checks for situations that can't happen if submdspan
is called with __slice_cast'ed slices. (Therefore, we might
not generate ("nice") diagnostics in cases where the user
directly calls submdspan_mapping.)

I think the mistake I've been making is that I wasn't
realizing how sliceable-mapping applies:

   - our submdspan_mapping strictly require valid canonicalized
   slices, but we'll choose to check less.

   - for user-provided layout mappings, submdspan_mapping is only
   restricted by "M models sliceable-mapping". We might pass them
   out-of-range slices; but that's their problem. We also tell

That's wrong, canonical-slice can produce out-of-range slices,
but submdspan_canonical_slice has the mandate/prerequisite that
the input, after casting, is a valid slice (type) for the k-th
extent.

   them exactly what we'll pass them.

I'll fix 02/10 and send that one alone.

On 11/21/25 13:17, Tomasz Kaminski wrote:
Now on submdspan_mapping, this is not meant to be "entry" point, but later
"exit" point,
ie. function that is called by the standard library internally for any
mapping that support
slicing (sliceable-mapping). It's does not meant to be user-facing, and
this is why it is
invoked by ADL, rather than being a simple public member, named submapping,

This is also why it has ugly name, because it is essentially global. I have
submitted an NB
comment, that was accepted to rename submdspan_extents to simply
subextents,
and submdspan_cannonicalize_slices to simply cannonicalize_slices, to
cleary indicate the
distinction between user-facing API and internal ones. If we believe that
they are use-cases
for getting submapping (like subextents) directly, we should have
std::submapping function,
that would do canonicalization, validation and then call submdspan_mapping
by ADL.

The sliceable mapping requirements says that the call to this function
needs to be well-formed
(and give you sliced mapping), when passed a valid (also in runtime sense)
set of arguments.
We promise to never call you out-of-contract, so it is fine to have UB, be
ill-formed, e.t.c.
This is also done to avoid mapping provided to implement duplicate checks
for validity
of the slice types or values, especially the later, as this will include a
runtime cost.

This will normally be a point when the requirements end - you are free to
do whatever is found
most convenient to you to get the "in-contract" values right. However, in
this specific case, we
may want to add a new slice categories (new template) in the future, and we
would like
pre-existing user mappings to fail loudly (at compile time) if they see
them as input, instead of
of usuall do whatever (including UB). This will why we have requirements on
mapping being
ill-formed on invalid-slices, however invalid-slices were constructed to be
very easy and cheap
to check at compile time (do not depend on extent at all), i.e. something
that is not any of:
   * full_extent
   * index_type
   * some constant_wrappeer (do not need to check type or value)
   * some strided_slice


Indeed a silly oversight on my part, and much more obvious
after you explained "satisfies" and "models" =)

Now, standard provides some mapping that are sliceable, so we also need to
provide this
exit point, but I think we should not repated checks that are already done,
as this will
pesimize "99.99%" use cases, to support few "out-of-label" uses.




Hope that clarifies, regards,
Tomasz


On Fri, Nov 21, 2025 at 9:37 AM Tomasz Kaminski <[email protected]> wrote:

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