On Mon, 13 Jul 2026 at 12:20, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> On Mon, Jul 13, 2026 at 12:51 PM Jonathan Wakely <[email protected]> wrote:
>>
>> On Fri, 10 Jul 2026 at 16:39, Tomasz Kamiński <[email protected]> wrote:
>> >
>> > This add an assertions checking if the combination of strides
>> > and extents passed to layout_stride::mapping is unique.
>> >
>> > To avoid excessive performance impact, in non-debug mode only
>> > checks that are are O(rank) (i.e. similar cost as operator())
>> > are performed. To achieve that, for the ranks greater or equal,
>> > we perform two separate checks:
>> > * required_span_size() > mdspan::__size(_M_extents) - this is
>> >   required by not sufficient condition, that is O(rank) and
>>
>> "a necessary but not sufficient condition"
>>
>> >   is always performed.
>> > * building an permutation using insertion-sort based algorithm,
>>
>> "a permutation"
>>
>> >   this is O(rank^2) and is performed only in debug modes.
>> >
>> > The check for non-zero stride values (performed for all non-zero
>> > ranks) is done only if the size of the multidimensional index space
>> > is not empty. This follows the resolution of LWG4603, and provides
>> > consistent behavior for constructing layout_stride mapping from
>> > layout_left/right directly or from strides extracted from it.
>> >
>> > libstdc++-v3/ChangeLog:
>> >
>> >         * include/std/mdspan (layout_stride::mapping::_M_check_unique):
>> >         Define.
>> >         (layout_stride::mapping(const extents_type&, span<....>):
>> >         Add __glibcxx_assert on _M_check_unique.
>> >         * testsuite/23_containers/mdspan/layouts/stride_neg.cc:
>> >         Test for the new added assertions.
>> > ---
>> > v3 removes the separate stride_unique.cc test, and uses dg-bogus
>> > instead (Jakub's no_specializations patch inspired me to do that).
>> >
>> >  libstdc++-v3/include/std/mdspan               | 75 ++++++++++++++++++-
>> >  .../mdspan/layouts/stride_neg.cc              | 72 +++++++++++++++++-
>> >  2 files changed, 142 insertions(+), 5 deletions(-)
>> >
>> > diff --git a/libstdc++-v3/include/std/mdspan 
>> > b/libstdc++-v3/include/std/mdspan
>> > index 23304e71b9d..30a079ca1ba 100644
>> > --- a/libstdc++-v3/include/std/mdspan
>> > +++ b/libstdc++-v3/include/std/mdspan
>> > @@ -1893,6 +1893,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> >           for (size_t __i = 0; __i < extents_type::rank(); ++__i)
>> >             _M_strides[__i] =
>> >               
>> > __mdspan::__index_type_cast<index_type>(as_const(__strides[__i]));
>> > +
>> > +         __glibcxx_assert(_M_check_unique());
>> >         }
>> >
>> >        template<typename _OIndexType>
>> > @@ -2027,6 +2029,75 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> >         }
>> >
>> >      private:
>> > +      using _Strides = typename __array_traits<index_type,
>> > +                                              
>> > extents_type::rank()>::_Type;
>> > +
>> > +      constexpr bool
>> > +      _M_check_unique() const
>> > +      {
>> > +       constexpr size_t __rank = extents_type::rank();
>> > +       if constexpr (__rank == 0)
>> > +         return true;
>> > +       // _GLIBCXX_RESOLVE_LIB_DEFECTS
>> > +       // 4603. `layout_stride` should accept zero strides for empty 
>> > `extents`
>> > +       else if (__mdspan::__empty(_M_extents))
>> > +         return true;
>> > +       else if constexpr (__rank == 1)
>> > +         return _M_strides[0] > 0;
>> > +       else if (__mdspan::__contains_zero<const index_type, 
>> > __rank>(_M_strides))
>> > +         return false;
>> > +       else if constexpr (__rank == 2)
>> > +         return (_M_strides[1] >= _M_strides[0] * _M_extents.extent(0))
>> > +             || (_M_strides[0] >= _M_strides[1] * _M_extents.extent(1));
>> > +       else
>> > +         {
>> > +           // This is necessary, but not sufficient condition for 
>> > uniquness.
>>
>> "uniqueness"
>>
>> > +           // It requires computation in range of call of operator(), so 
>> > check
>> > +           // if first, also in non-debug mode.
>> > +           if (required_span_size() < __mdspan::__size(_M_extents))
>> > +             return false;
>> > +
>> > +           // Checking if pertmuation of strides exists is O(rank^2) so 
>> > enabled
>>
>> "permutation"
>>
>> but I'm not sure what this comment is saying, are we checking for a
>> non-unique permutation of strides? An invalid permutation of strides?
>
> I mean checking if permutation described in 
> https://eel.is/c++draft/mdspan.layout#stride.cons-4.3
> exists. Not sure how to comment that.

Let's say "Checking for a valid permutation of strides is (rank^2) so ..."

OK with that and the other typo fixes.


>>
>>
>> > +           // only in debug mode.
>> > +#ifdef _GLIBCXX_DEBUG
>> > +           size_t __perm[__rank];
>> > +           for (size_t __i = 0; __i < __rank; ++__i)
>> > +             __perm[__i] = __i;
>> > +
>> > +           auto __popmin = [&](size_t __from)
>> > +           {
>> > +             size_t __min = __perm[__rank - 1];
>> > +             for (size_t __i = __rank - 1; __i > __from; --__i)
>> > +               {
>> > +                 const size_t __cur = __perm[__i - 1];
>> > +                 const auto __res = _M_strides[__cur] <=> 
>> > _M_strides[__min];
>> > +                 // Equal strides are allowed only for extent one, so 
>> > apply it first.
>> > +                 if (__res < 0 || (__res == 0 && _M_extents.extent(__cur) 
>> > == 1))
>> > +                   {
>> > +                     __perm[__i] = __min;
>> > +                     __min = __cur;
>> > +                   }
>> > +                 else
>> > +                   __perm[__i] = __cur;
>> > +               }
>> > +             __perm[__from] = __min;
>> > +             return __min;
>> > +           };
>> > +
>> > +           // We use eager insertion-sort based algorithm to permute the 
>> > __perm.
>> > +           size_t __prev = __popmin(0);
>> > +           for (size_t __i = 1; __i < __rank; ++__i)
>> > +             {
>> > +               const size_t __next = __popmin(__i);
>> > +               if (_M_strides[__next] < _M_strides[__prev] * 
>> > _M_extents.extent(__prev))
>> > +                 return false;
>> > +               __prev = __next;
>> > +             }
>> > +#endif
>> > +           return true;
>> > +         }
>> > +      }
>> > +
>> >  #if __glibcxx_submdspan
>> >        template<typename... _Slices>
>> >         requires (extents_type::rank() == sizeof...(_Slices))
>> > @@ -2047,8 +2118,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> >         }
>> >  #endif
>> >
>> > -      using _Strides = typename __array_traits<index_type,
>> > -                                              
>> > extents_type::rank()>::_Type;
>> >        [[no_unique_address]] extents_type _M_extents;
>> >        [[no_unique_address]] _Strides _M_strides;
>> >      };
>> > @@ -3433,7 +3502,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> >               __mdspan::__check_inrange_index(__ext, __slice.offset);
>> >             else if constexpr (__is_constant_wrapper_v<typename 
>> > _Slice::extent_type>)
>> >               {
>> > -               __mdspan::__check_inrange_index(__ext, __slice.offset);
>> > +               __mdspan::__check_inrange_index(__ext, __slice.offset);
>> >                 if constexpr (__is_constant_wrapper_v<typename 
>> > _Slice::stride_type>)
>> >                   static_assert(_Slice::stride_type::value > 0);
>> >                 else
>> > diff --git 
>> > a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc 
>> > b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
>> > index 153560bc82f..692caeac812 100644
>> > --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
>> > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
>> > @@ -1,7 +1,5 @@
>> >  // { dg-do compile { target c++23 } }
>> >  #include <mdspan>
>> > -
>> > -#include "../layout_traits.h"
>> >  #include <cstdint>
>> >
>> >  constexpr size_t dyn = std::dynamic_extent;
>> > @@ -27,5 +25,75 @@ test_stride_negative()
>> >  }
>> >  static_assert(test_stride_negative()); // { dg-error "expansion of" }
>> >
>> > +
>> > +using std::array;
>> > +template<size_t Rank>
>> > + using mapping = std::layout_stride::mapping<std::dextents<size_t, Rank>>;
>> > +
>> > +constexpr auto m0 = mapping<0>(array<int, 0>{}, array<int, 0>{}); // { 
>> > dg-bogus "expansion of" }
>> > +
>> > +// Zero strides allowed for empty mappings
>> > +constexpr auto m00 = mapping<1>(array{0}, array{0}); // { dg-bogus 
>> > "expansion of" }
>> > +constexpr auto m01 = mapping<2>(array{0, 1}, array{3, 0}); // { dg-bogus 
>> > "expansion of" }
>> > +constexpr auto m02 = mapping<2>(array{0, 1}, array{0, 0}); // { dg-bogus 
>> > "expansion of" }
>> > +constexpr auto m03 = mapping<3>(array{0, 1, 2}, array{3, 0, 0}); // { 
>> > dg-bogus "expansion of" }
>> > +constexpr auto m04 = mapping<3>(array{0, 1, 2}, array{3, 3, 0}); // { 
>> > dg-bogus "expansion of" }
>> > +constexpr auto m05 = mapping<4>(array{0, 1, 2, 3}, array{5, 3, 0, 2}); // 
>> > { dg-bogus "expansion of" }
>> > +
>> > +// Otherwise leads to non-unique mappings.
>> > +constexpr auto m06 = mapping<1>(array{1}, array{0});                   // 
>> > { dg-error "expansion of" }
>> > +constexpr auto m07 = mapping<2>(array{1, 2}, array{3, 0});             // 
>> > { dg-error "expansion of" }
>> > +constexpr auto m08 = mapping<3>(array{1, 2, 3}, array{3, 3, 0});       // 
>> > { dg-error "expansion of" }
>> > +constexpr auto m09 = mapping<4>(array{1, 2, 3, 5}, array{5, 3, 0, 2}); // 
>> > { dg-error "expansion of" }
>> > +
>> > +// required_span_size is smaller size of multidimensional index space
>> > +constexpr auto m10 = mapping<2>(array{3, 4}, array{2, 3}); // { dg-error 
>> > "expansion of" }
>> > +constexpr auto m11 = mapping<2>(array{3, 4}, array{1, 2}); // { dg-error 
>> > "expansion of" }
>> > +constexpr auto m12 = mapping<3>(array{3, 4, 5}, array{1, 3, 11}); // { 
>> > dg-error "expansion of" }
>> > +constexpr auto m13 = mapping<3>(array{3, 4, 5}, array{1, 2, 12}); // { 
>> > dg-error "expansion of" }
>> > +constexpr auto m14 = mapping<4>(array{3, 4, 5, 6}, array{1, 3, 12, 50}); 
>> > // { dg-error "expansion of" }
>> > +constexpr auto m15 = mapping<4>(array{3, 4, 5, 6}, array{1, 3, 11, 60}); 
>> > // { dg-error "expansion of" }
>> > +constexpr auto m16 = mapping<4>(array{3, 4, 5, 6}, array{1, 2, 12, 60}); 
>> > // { dg-error "expansion of" }
>> > +
>> > +// Passes required_span_size check (smaller and bigger stride), detected 
>> > only in debug mode
>> > +constexpr auto m17 = mapping<3>(array{3, 4, 5}, array{1, 2, 25}); // { 
>> > dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m18 = mapping<3>(array{3, 4, 5}, array{1, 5, 12}); // { 
>> > dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m19 = mapping<4>(array{3, 4, 5, 6}, array{1, 2, 12, 100}); 
>> > // { dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m20 = mapping<4>(array{3, 4, 5, 6}, array{1, 5, 12, 60});  
>> > // { dg-error "expansion of" "" { target debug_mode } }
>> > +
>> > +// Equal indicies allowed if extents are 1
>> > +constexpr auto m21 = mapping<2>(array{5, 1}, array{1, 1}); // { dg-bogus 
>> > "expansion of" }
>> > +constexpr auto m22 = mapping<3>(array{1, 5, 1}, array{1, 1, 1}); // { 
>> > dg-bogus "expansion of" }
>> > +constexpr auto m23 = mapping<4>(array{1, 1, 5, 1}, array{1, 1, 1, 1}); // 
>> > { dg-bogus "expansion of" }
>> > +constexpr auto m24 = mapping<5>(array{3, 4, 1, 1, 5}, array{3, 10, 3, 3, 
>> > 45}); // { dg-bogus "expansion of" }
>> > +constexpr auto m25 = mapping<5>(array{3, 4, 1, 1, 5}, array{3, 10, 3, 10, 
>> > 45}); // { dg-bogus "expansion of" }
>> > +constexpr auto m26 = mapping<5>(array{3, 4, 1, 1, 5}, array{3, 10, 10, 3, 
>> > 45}); // { dg-bogus "expansion of" }
>> > +constexpr auto m27 = mapping<5>(array{3, 4, 1, 1, 5}, array{3, 10, 10, 
>> > 10, 45}); // { dg-bogus "expansion of" }
>> > +
>> > +// Only some permutations are valid, invalid permutations detected only 
>> > in debug mode.
>> > +
>> > +// Unique if stride 3 corresponds to extent 3
>> > +constexpr auto m28 = mapping<2>(array{3, 4}, array{3, 10}); // { dg-bogus 
>> > "expansion of" }
>> > +constexpr auto m29 = mapping<2>(array{3, 4}, array{10, 3}); // { dg-error 
>> > "expansion of" "" { target debug_mode } }
>> > +constexpr auto m30 = mapping<2>(array{4, 3}, array{10, 3}); // { dg-bogus 
>> > "expansion of" }
>> > +constexpr auto m31 = mapping<2>(array{4, 3}, array{3, 10}); // { dg-error 
>> > "expansion of" "" { target debug_mode } }
>> > +
>> > +// Unique if stride 11 corresponds to extent 3, as 11 * 3 < 35, and 2 * 5 
>> > < 11
>> > +constexpr auto m32 = mapping<3>(array{3, 4, 5}, array{11, 2, 35}); // { 
>> > dg-bogus "expansion of" }
>> > +constexpr auto m33 = mapping<3>(array{3, 4, 5}, array{11, 35, 2}); // { 
>> > dg-bogus "expansion of" }
>> > +constexpr auto m34 = mapping<3>(array{3, 4, 5}, array{2, 11, 35}); // { 
>> > dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m35 = mapping<3>(array{3, 4, 5}, array{35, 2, 11}); // { 
>> > dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m36 = mapping<3>(array{5, 4, 3}, array{2, 35, 11}); // { 
>> > dg-bogus "expansion of" }
>> > +constexpr auto m37 = mapping<3>(array{5, 4, 3}, array{2, 11, 35}); // { 
>> > dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m38 = mapping<3>(array{5, 3, 4}, array{35, 11, 2}); // { 
>> > dg-bogus "expansion of" }
>> > +constexpr auto m39 = mapping<3>(array{3, 5, 4}, array{35, 2, 11}); // { 
>> > dg-error "expansion of" "" { target debug_mode } }
>> > +
>> > +// Unique for stride -> extent mapping: 3 * 5 < 16, 15 * 4 < 65, 65 * 3 < 
>> > 200
>> > +constexpr auto m40 = mapping<4>(array{3, 4, 5, 6}, array{65, 16, 3, 
>> > 200}); // { dg-bogus "expansion of" }
>> > +constexpr auto m41 = mapping<4>(array{3, 4, 5, 6}, array{3, 16, 65, 
>> > 200}); // { dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m42 = mapping<4>(array{3, 4, 5, 6}, array{200, 65, 3, 
>> > 16}); // { dg-error "expansion of" "" { target debug_mode } }
>> > +constexpr auto m43 = mapping<4>(array{5, 3, 6, 4}, array{3, 65, 200, 
>> > 16}); // { dg-bogus "expansion of" }
>> > +constexpr auto m44 = mapping<4>(array{5, 3, 6, 4}, array{3, 16, 200, 
>> > 65}); // { dg-error "expansion of" "" { target debug_mode } }
>> > +
>> >  // { dg-prune-output "non-constant condition for static assertion" }
>> >  // { dg-prune-output "__glibcxx_assert_fail()" }
>> > --
>> > 2.54.0
>> >
>>

Reply via email to