On 9/9/25 15:54, Tomasz Kaminski wrote:
On Mon, Sep 8, 2025 at 10:14 PM Luc Grosheintz <luc.groshei...@gmail.com>
wrote:

I decided to deviate once (described in detail in the commit message).
Without this deviation writing tests is very clumsy and sometimes not
possible, e.g. when mixing uint8_t and dynamic padding values to check
mandates w.r.t. representability. Previously, we discussed other ways of
fixing the issue. The reason I chose this version is because I believe
it's the smallest deviation from the standard; and we can deviate
differently in followup commits.

   ---------------------------------------------------------

The tests contain a function:

     constexpr bool
     is_same_mapping(const auto& lhs, const auto& rhs)
       // ...
       for (size_t i = 0; i < lhs.extents().rank(); ++i)
         if (lhs.stride(i) != rhs.stride(i))
           return false;
       return true;
     }

The above is accidentally quadratic, because computing all strides can
be done in linear time, but not if one computes them one by one, then
it's quadratic. It's annoyingly hard to do in linear time, because it
requires both precise knowledge of the definition of `stride` for each
layout; and ideally it also requires access to implementation details
related to how we separate static and dynamic extents to be able to
compute static products at compile time.

Do we need a member strides() for all strided layout mappings to avoid
the issue?

   ---------------------------------------------------------

The left-padded conversion rules are unexpected in two ways:

1. It allows conversion between mappings that have extent_types that
aren't convertable (because it's not guaranteed at compile-time that the
conversion is safe). AFAICT, this is covered by LWG4272 [1].
[1]: https://cplusplus.github.io/LWG/issue4272

2. The rules for converting between left-padded layouts with different
padding values is different. For extents the rule is to allow sta ->
sta, sta -> dyn and dyn -> dyn; but prevent dyn -> sta (because it can't
be guaranteed at compile-time that the dynamic padding values matches
the dynamic padding value match). For padding values we also disallow
conversion for dyn -> dyn.

   ---------------------------------------------------------

If the padding value is statically 0 or 1, then it's effectively never
padded, because:

     least_multiple_as_least(padding_value, extent(0)) == extent(0)

either because of a special case (for padding_value == 0), or because `n
% 1 == 0` and `n / 1 == n` (for padding_value == 1). Put differently,
it's always exhaustive. However, is_always_exhaustive isn't true if
exts.extent(0) is a dynamic extent. Even though that has no impact for
padding_value <= 1.

   ---------------------------------------------------------

It's not possible to round-trip: layout_right -> layout_left_padded ->
layout_right even for rank <= 1 where usually the difference between
different mappings is ignored.

   ---------------------------------------------------------

There's similar problems with operator== as noted before; and for which
I promised to (and never did) report a non-editorial issue.

--- 8< ---

This commit adds a new layout layout_left_padded as standardized in
N5014 but with one deviation. This includes checking of mandates and
prerequisites, with exception of "slow" prerequisites, i.e. those that
requires a for loop. It adds the feature testing macro submdspan as a
purely internal macro and registers layout_left_padded in the std
module.

The standard mandates that the padding_value is representable as
index_type. However, padding_value can be dynamic_extent which is
defined as `size_t(-1)`. Hence, it's impossible to use a dynamic
padding_value if index_type is int. The deviation is to only require
static padding values to be representable as index_type; and thereby
allow dynamic padding values for every index_type.

libstdc++-v3/ChangeLog:

         * include/bits/version.def (submdspan): Add as internal
         only.
         * include/bits/version.h: Regenerate.
         * include/std/mdspan (__fwd_prod): New overload.
         (layout_left_padded): Add declaration and implementation.
         (layout_right_padded): Add declaration only.
         (__is_left_padded_mapping): New concept.
         (__is_right_padded_mapping): Ditto.
         (__standardized_mapping): Recognize left and right padded
         mappings.
         (layout_left::mapping::mapping): New overload for left
         padded mappings.
         * src/c++23/std.cc.in (layout_left_padded): Add.
         * testsuite/23_containers/mdspan/layouts/ctors.cc: Add tests for
         layout_left_padded.
         * testsuite/23_containers/mdspan/layouts/empty.cc: Ditto.
         * testsuite/23_containers/mdspan/layouts/mapping.cc: Ditto.
         * testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc: New
test.
         * testsuite/23_containers/mdspan/layouts/padded.cc: New test.
         * testsuite/23_containers/mdspan/layouts/padded_neg.cc: New test.

Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com>
---
  libstdc++-v3/include/bits/version.def         |   9 +
  libstdc++-v3/include/bits/version.h           |   9 +
  libstdc++-v3/include/std/mdspan               | 416 +++++++++++++-
  libstdc++-v3/src/c++23/std.cc.in              |   8 +-
  .../23_containers/mdspan/layouts/ctors.cc     |  61 +-
  .../mdspan/layouts/debug/padded_neg.cc        |   3 +
  .../23_containers/mdspan/layouts/empty.cc     |  10 +-
  .../23_containers/mdspan/layouts/mapping.cc   | 198 +++++--
  .../23_containers/mdspan/layouts/padded.cc    | 537 ++++++++++++++++++
  .../mdspan/layouts/padded_neg.cc              | 225 ++++++++
  10 files changed, 1414 insertions(+), 62 deletions(-)
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc

diff --git a/libstdc++-v3/include/bits/version.def
b/libstdc++-v3/include/bits/version.def
index 65b9a278776..1895078210a 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1050,6 +1050,15 @@ ftms = {
      cxxmin = 26;
      extra_cond = "__glibcxx_assume_aligned "
      "&& __glibcxx_is_sufficiently_aligned";
+    };
+};
+
+ftms = {
+  name = submdspan;
+  no_stdname = true; // FIXME: remove
+  values = {
+    v = 202306;
+    cxxmin = 26;
    };
  };

diff --git a/libstdc++-v3/include/bits/version.h
b/libstdc++-v3/include/bits/version.h
index b05249857d2..c6de494da08 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1178,6 +1178,15 @@
  #endif /* !defined(__cpp_lib_aligned_accessor) &&
defined(__glibcxx_want_aligned_accessor) */
  #undef __glibcxx_want_aligned_accessor

+#if !defined(__cpp_lib_submdspan)
+# if (__cplusplus >  202302L)
+#  define __glibcxx_submdspan 202306L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_submdspan)
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_submdspan) &&
defined(__glibcxx_want_submdspan) */
+#undef __glibcxx_want_submdspan
+
  #if !defined(__cpp_lib_ssize)
  # if (__cplusplus >= 202002L)
  #  define __glibcxx_ssize 201902L
diff --git a/libstdc++-v3/include/std/mdspan
b/libstdc++-v3/include/std/mdspan
index 678b2619ebe..1270640ab6b 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -44,6 +44,7 @@

  #define __glibcxx_want_mdspan
  #define __glibcxx_want_aligned_accessor
+#define __glibcxx_want_submdspan
  #include <bits/version.h>

  #ifdef __glibcxx_mdspan
@@ -476,6 +477,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        }

      // Preconditions: _r < _Extents::rank()
+    template<typename _Extents>
+      constexpr typename _Extents::index_type
+      __fwd_prod(const _Extents& __exts, size_t __begin, size_t __end)
noexcept
+      {
+       size_t __sta_prod = [__begin, __end] {
+         span<const size_t> __sta_exts = __static_extents<_Extents>();
+         size_t __ret = 1;
+         for(auto __ext : __sta_exts.subspan(__begin, __end - __begin))
+           if (__ext != dynamic_extent)
+             __ret *= __ext;
+         return __ret;
+       }();
+       return __extents_prod(__exts, __sta_prod, __begin, __end);
+      }
+
      template<typename _Extents>
        constexpr typename _Extents::index_type
        __fwd_prod(const _Extents& __exts, size_t __r) noexcept
@@ -567,6 +583,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        class mapping;
    };

+#ifdef __glibcxx_submdspan
+  template<size_t _PaddingValue>
+    struct layout_left_padded
+    {
+      template<typename _Extents>
+       class mapping;
+    };
+
+  template<size_t _PaddingValue>
+    struct layout_right_padded
+    {
+      template<typename _Extents>
+       class mapping;
+    };
+#endif
+
    namespace __mdspan
    {
      template<typename _Tp>
@@ -660,10 +692,42 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         is_same_v<typename _Layout::template mapping<typename
_Mapping::extents_type>,
                   _Mapping>;

+    template<template<size_t> typename _Layout, typename _Mapping>
+      concept __is_padded_mapping_of = requires
+      {
+       typename _Mapping::extents_type;
+       { _Mapping::padding_value } -> same_as<const size_t&>;
+      }
+      && same_as<_Mapping, typename
_Layout<_Mapping::padding_value>::mapping<
+         typename _Mapping::extents_type>>;
+
+#ifdef __glibcxx_submdspan
+    template<typename _Mapping>
+      constexpr bool __is_left_padded_mapping = __is_padded_mapping_of<
+       layout_left_padded, _Mapping>;
+
+    template<typename _Mapping>
+      constexpr bool __is_right_padded_mapping = __is_padded_mapping_of<
+       layout_right_padded, _Mapping>;
+#else
+    template<typename _Mapping>
+      constexpr bool __is_left_padded_mapping = false;
+
+    template<typename _Mapping>
+      constexpr bool __is_right_padded_mapping = false;
+#endif
+
+    template<typename _PaddedMapping>
+      constexpr size_t
+      __get_static_stride()
+      { return _PaddedMapping::_S_static_stride; }
+
      template<typename _Mapping>
        concept __standardized_mapping = __mapping_of<layout_left, _Mapping>
                                        || __mapping_of<layout_right,
_Mapping>
-                                      || __mapping_of<layout_stride,
_Mapping>;
+                                      || __mapping_of<layout_stride,
_Mapping>
+                                      ||
__is_left_padded_mapping<_Mapping>
+                                      ||
__is_right_padded_mapping<_Mapping>;

      // A tag type to create internal ctors.
      class __internal_ctor
@@ -717,6 +781,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         : mapping(__other.extents(), __mdspan::__internal_ctor{})
         { __glibcxx_assert(*this == __other); }

+      template<class _LeftPaddedMapping>
+       requires __mdspan::__is_left_padded_mapping<_LeftPaddedMapping>
+                && is_constructible_v<extents_type,
+                                      typename
_LeftPaddedMapping::extents_type>
+       constexpr
+       explicit(!is_convertible_v<typename
_LeftPaddedMapping::extents_type,
+                                  extents_type>)
+       mapping(const _LeftPaddedMapping& __other) noexcept
+       : mapping(__other.extents(), __mdspan::__internal_ctor{})
+       {
+         constexpr size_t __ostride_sta = __mdspan::__get_static_stride<
+           _LeftPaddedMapping>();
+
+         if constexpr (extents_type::rank() > 1
+             && extents_type::static_extent(0) != dynamic_extent
+             && __ostride_sta != dynamic_extent)
+           static_assert(extents_type::static_extent(0) == __ostride_sta);
+
+         __glibcxx_assert(extents_type::rank() <= 1
+           || __other.stride(1) == __other.extents().extent(0));
+       }
+
        constexpr mapping&
        operator=(const mapping&) noexcept = default;

@@ -1164,6 +1250,334 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        [[no_unique_address]] _S_strides_t _M_strides;
      };

+#ifdef __glibcxx_submdspan
+  namespace __mdspan
+  {
+    template<typename _Io, typename _I1, typename _I2>
+      constexpr _Io
+      __least_multiple_at_least(_I1 __x, _I2 __y)
+      {
+       if (__x == 0)
+         return static_cast<_Io>(__y);
+       return static_cast<_Io>((__y / __x + (__y % __x != 0)) * __x);
+      }
+
+    template<typename _Extents, typename _Stride, typename... _Indices>
+      constexpr typename _Extents::index_type
+      __linear_index_leftpad(const _Extents& __exts, _Stride __stride,
+                            _Indices... __indices)
+      {
+       // i0 + stride*(i1 + extents.extent(1)*...)
+       using _IndexType = typename _Extents::index_type;
+       _IndexType __res = 0;
+       if constexpr (sizeof...(__indices) > 0)
+         {
+           _IndexType __mult = 1;
+
+           auto __update_rest = [&, __pos = 1u](_IndexType __idx) mutable
+             {
+               __res += __idx * __mult;
+               __mult *= __exts.extent(__pos);
+               ++__pos;
+             };
+
+           auto __update = [&](_IndexType __idx, auto... __rest)
+             {
+               __res += __idx;
+               __mult = __stride.extent(0);
+               (__update_rest(__rest), ...);
+             };
+           __update(__indices...);
+         }
+       return __res;
+      }
+  }
+
+  template<size_t _PaddingValue>
+    template<typename _Extents>
+      class layout_left_padded<_PaddingValue>::mapping
+      {
+      public:
+       static constexpr size_t padding_value = _PaddingValue;
+
+       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;
+       using layout_type = layout_left_padded<padding_value>;
+
+       static_assert(__mdspan::__representable_size<extents_type,
index_type>,
+         "The size of extents_type must be representable as index_type");
+
+       // DEVIATION the standard requires that _PaddingValue is
representable
+       // as index_type. This seems unlikely, because if index_type <
size_t
+       // then the mandate is violated for `padding_value ==
dynamic_extent`.
+       // Which means that padding_value == dynamic_extent only reliably
work
+       // if index_type == size_t (e.g. it fails for index_type == int on
a
+       // 64bit system).
+       static_assert((padding_value == dynamic_extent)
+           || (padding_value <= numeric_limits<index_type>::max()),
+         "padding_value must be representable as index_type");
+
+      private:
+       static constexpr size_t _S_rank = _Extents::rank();
+       static constexpr size_t _S_static_stride = [] consteval
+       {
+         if constexpr (_S_rank <= 1)
+           return size_t(0);
+         else if constexpr (padding_value == dynamic_extent ||
+                       _Extents::static_extent(0) == dynamic_extent)
+           return dynamic_extent;
+         else
+           return
__mdspan::__least_multiple_at_least<size_t>(padding_value,
+             size_t(_Extents::static_extent(0)));
+       }();
+
+       constexpr friend size_t
+       __mdspan::__get_static_stride<mapping>();
+
+      public:
+       constexpr
+       mapping() noexcept
+       : mapping(extents_type{})
+       { }
+
+       constexpr
+       mapping(const mapping&) noexcept = default;
+
+       constexpr
+       mapping(const extents_type& __exts)
+       : _M_extents(__exts)
+       {
+
  __glibcxx_assert(__mdspan::__is_representable_extents(_M_extents));
+         if (_S_rank > 1)
+           {
+             index_type __stride;
+             if constexpr (padding_value == dynamic_extent)
+               __stride = __exts.extent(0);
+             else
+               __stride = __mdspan::__least_multiple_at_least<index_type>(
+                 padding_value, __exts.extent(0));
+
+             _M_stride = std::extents<index_type,
_S_static_stride>{__stride};
+           }
+       }
+
+       template<__mdspan::__valid_index_type<index_type> _OIndexType>
+         constexpr mapping(const extents_type& __exts, _OIndexType __opad)
+         : _M_extents(__exts)
+         {
+           if constexpr (std::is_integral_v<_OIndexType>)
+             {
+               __glibcxx_assert(cmp_less_equal(__opad,
+                   numeric_limits<index_type>::max()));
+               if constexpr (std::is_signed_v<_OIndexType>)
+                 __glibcxx_assert(__opad >= 0);
+             }
+
+           auto __pad = static_cast<index_type>(std::move(__opad));
+           if constexpr (std::is_signed_v<index_type>)
+             __glibcxx_assert(__pad >= 0);
+           if constexpr (padding_value != dynamic_extent)
+             __glibcxx_assert(cmp_equal(padding_value, __pad));
+           if constexpr (_S_rank > 1)
+             _M_stride = _S_stride_type{
+               __mdspan::__least_multiple_at_least<index_type>(__pad,
+                 __exts.extent(0))};
+         }
+
+       template<typename _OExtents>
+         requires is_constructible_v<extents_type, _OExtents>
+         constexpr explicit(!is_convertible_v<_OExtents, extents_type>)
+         mapping(const layout_left::mapping<_OExtents>& __other)
+         : mapping(extents_type(__other.extents()))
+         {
+           if constexpr (_OExtents::rank() > 1)
+             static_assert(_S_static_stride == dynamic_extent
+                 || _OExtents::static_extent(0) == dynamic_extent
+                 || _S_static_stride == _OExtents::static_extent(0),
+               "(*this).stride(1) must be compatible with
other.stride(1)");
+
+           // REVIEW: can this one go, because we check
+           //   __is_representable_extents in mapping(const mapping&)
+           __glibcxx_assert(cmp_less_equal(__other.required_span_size(),
+
  numeric_limits<index_type>::max()));
+           __glibcxx_assert((!(_S_rank > 1 && padding_value !=
dynamic_extent))
+             || (_M_stride.extent(0) == __other.extents().extent(0)));
+         }
+
+       template<typename _OExtents>
+         requires is_constructible_v<_OExtents, extents_type>
+         constexpr explicit(_OExtents::rank() > 0)
+         mapping(const typename layout_stride::mapping<_OExtents>&
__other)
+         : _M_extents(__other.extents())
+         {
+           if constexpr (_S_rank > 1 && padding_value != dynamic_extent)
+             __glibcxx_assert(cmp_equal(__other.stride(1),
+
  __mdspan::__least_multiple_at_least<index_type>(padding_value,
+                   __other.extents().extent(0))));
+
+           if constexpr (_S_rank > 0)
+             __glibcxx_assert(__other.stride(0) == 1);
+
+           __glibcxx_assert(cmp_less_equal(__other.required_span_size(),
+
  numeric_limits<index_type>::max()));
+
+           if constexpr (_S_rank > 1 && _S_static_stride ==
dynamic_extent)
+             _M_stride = _S_stride_type{__other.stride(1)};
+         }
+
+       template<typename _LeftPaddedMapping>
+         requires __mdspan::__is_left_padded_mapping<_LeftPaddedMapping>
+             && is_constructible_v<extents_type,
+                                   typename
_LeftPaddedMapping::extents_type>
+         constexpr explicit(_S_rank > 1 && (padding_value !=
dynamic_extent
+               || _LeftPaddedMapping::padding_value == dynamic_extent))
+         mapping(const _LeftPaddedMapping& __other)
+         : _M_extents(__other.extents())
+         {
+           if constexpr (_S_rank > 1)
+           {
+             static_assert(padding_value == dynamic_extent
+                 || _LeftPaddedMapping::padding_value == dynamic_extent
+                 || padding_value == _LeftPaddedMapping::padding_value,
+               "If neither padding_value is dynamic_extent, then they
must "
+               "be equal");
+
+             __glibcxx_assert((padding_value == dynamic_extent
+                 || cmp_equal(__other.stride(1),
+                      __mdspan::__least_multiple_at_least<index_type>(
+                        padding_value, __other.extents().extent(0))))
+               && "other.stride(1) must be the next larger multiple of "
+                  "padding_value that is greater or equal to
other.extent(0)");
+           }
+           __glibcxx_assert(cmp_less_equal(__other.required_span_size(),
+               numeric_limits<index_type>::max()));
+
+           if constexpr (_S_rank > 1 && (_S_static_stride ==
dynamic_extent))
+             _M_stride = _S_stride_type{__other.stride(1)};
+         }
+
+       template<typename _RightPaddedMapping>
+         requires
(__mdspan::__is_right_padded_mapping<_RightPaddedMapping>
+             || __mdspan::__mapping_of<layout_right, _RightPaddedMapping>)
+           && (_S_rank <= 1)
+           && is_constructible_v<extents_type,
+                                 typename
_RightPaddedMapping::extents_type>
+         constexpr explicit(!is_convertible_v<
+             typename _RightPaddedMapping::extents_type, extents_type>)
+         mapping(const _RightPaddedMapping& __other) noexcept
+         : _M_extents(__other.extents())
+         {
+           __glibcxx_assert(cmp_less_equal(__other.required_span_size(),
+               numeric_limits<index_type>::max()));
+         }
+
+       constexpr mapping&
+       operator=(const mapping&) noexcept = default;
+
+       constexpr const extents_type&
+       extents() const noexcept { return _M_extents; }
+
+       constexpr array<index_type, _S_rank>
+       strides() const noexcept
+       {
+         array<index_type, _S_rank> __ret;
+         if constexpr (_S_rank > 0)
+           __ret[0] = 1;
+         if constexpr (_S_rank > 1)
+           __ret[1] = _M_stride.extent(0);
+         if constexpr (_S_rank > 2)
+           for(size_t __i = 2; __i < _S_rank; ++__i)
+             __ret[__i] = __ret[__i - 1] * _M_extents.extent(__i - 1);
+         return __ret;
+       }
+
+       constexpr index_type
+       required_span_size() const noexcept
+       {
+         if constexpr (_S_rank == 0)
+           return 1;
+         else if (__mdspan::__empty(_M_extents))
+           return 0;
+         else
+           return _M_stride.extent(0) * __mdspan::__rev_prod(_M_extents,
0)
+                  - (_M_stride.extent(0) - _M_extents.extent(0));
+       }
+
+       // _GLIBCXX_RESOLVE_LIB_DEFECTS
+       // 4314. Missing move in mdspan layout mapping::operator()
+       template<__mdspan::__valid_index_type<index_type>... _Indices>
+         requires (sizeof...(_Indices) == _S_rank)
+         constexpr index_type
+         operator()(_Indices... __indices) const noexcept
+         {
+           return __mdspan::__linear_index_leftpad(_M_extents, _M_stride,
+             static_cast<index_type>(std::move(__indices))...);
+         }
+
+       static constexpr bool
+       is_always_unique() noexcept { return true; }
+
+       static constexpr bool
+       is_always_exhaustive() noexcept
+       {
+         if constexpr (_S_rank <= 1)
+           return true;
+         else
+           return extents_type::static_extent(0) != dynamic_extent
+                  && _S_static_stride != dynamic_extent
+                  && extents_type::static_extent(0) == _S_static_stride;
+       }
+
+       static constexpr bool
+       is_always_strided() noexcept { return true; }
+
+       static constexpr bool
+       is_unique() noexcept { return true; }
+
+       constexpr bool
+       is_exhaustive() const noexcept
+       {
+         if constexpr (is_always_exhaustive())
+           return true;
+         else
+           return _M_extents.extent(0) == _M_stride.extent(0);
+       }
+
+       static constexpr bool
+       is_strided() noexcept { return true; }
+
+       constexpr index_type
+       stride(rank_type __r) const noexcept
+       {
+         __glibcxx_assert(__r < _S_rank);
+         if (__r == 0)
+           return 1;
+         else
+           return static_cast<index_type>(
+             static_cast<size_t>(_M_stride.extent(0)) *
+             static_cast<size_t>(__mdspan::__fwd_prod(_M_extents, 1,
__r)));
+       }
+
+       template<typename _LeftPaddedMapping>
+         requires(__mdspan::__is_left_padded_mapping<_LeftPaddedMapping>
+                  && _LeftPaddedMapping::extents_type::rank() == _S_rank)
+         friend constexpr bool
+         operator==(const mapping& __self, const _LeftPaddedMapping&
__other)
+         noexcept
+         {
+           return __self.extents() == __other.extents()
+             && (_S_rank < 2 || __self.stride(1) == __other.stride(1));
+         }
+
+      private:
+       using _S_stride_type = std::extents<index_type, _S_static_stride>;

Rename this to __stride_type.

True, I suspect there'll be more of those in scatter all over <mdspan>.
They'll also get cleaned up.

While we're on cleaning up: should I be writing docstrings? <span> doesn't
have them and I didn't even know there was a GNU C++ standard library
documentation. I always use cppreference, so I thought it was acceptable
to not document, because it's already documented elsewhere.


+       [[no_unique_address]] _S_stride_type _M_stride;
+       [[no_unique_address]] extents_type _M_extents{};
+      };
+#endif
+
    template<typename _ElementType>
      struct default_accessor
      {
diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/
std.cc.in
index a217a87330b..c2854cb1828 100644
--- a/libstdc++-v3/src/c++23/std.cc.in
+++ b/libstdc++-v3/src/c++23/std.cc.in
@@ -1869,9 +1869,11 @@ export namespace std
    using std::aligned_accessor;
  #endif
    using std::mdspan;
-  // FIXME layout_left_padded, layout_right_padded, strided_slice,
-  // submdspan_mapping_result, full_extent_t, full_extent,
submdspan_extents,
-  // mdsubspan
+#if __glibcxx_submdspan
+  using std::layout_left_padded;
+#endif
+  // FIXME layout_right_padded, strided_slice, submdspan_mapping_result,
+  // full_extent_t, full_extent, submdspan_extents, mdsubspan
  }
  #endif

diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
index 23c0a55dae1..290c53647cf 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
@@ -6,6 +6,16 @@

  constexpr size_t dyn = std::dynamic_extent;

+template<typename Layout>
+  constexpr bool
+  is_layout_leftpad = false;
+
+#ifdef __glibcxx_submdspan
+template<size_t Pad>
+  constexpr bool
+  is_layout_leftpad<std::layout_left_padded<Pad>> = true;
+#endif
+
  template<typename Mapping, typename IndexType, size_t... Extents>
    constexpr void
    verify(std::extents<IndexType, Extents...> oexts)
@@ -27,7 +37,6 @@ template<typename Mapping, typename OMapping>
         VERIFY(std::cmp_equal(m.stride(i), other.stride(i)));
    }

-
  template<typename To, typename From>
    constexpr void
    verify_convertible(From from)
@@ -40,7 +49,10 @@ template<typename To, typename From>
    constexpr void
    verify_nothrow_convertible(From from)
    {
-    static_assert(std::is_nothrow_constructible_v<To, From>);
+    if constexpr (is_layout_leftpad<typename To::layout_type>)
+      static_assert(std::is_constructible_v<To, From>);
+    else
+      static_assert(std::is_nothrow_constructible_v<To, From>);
      verify_convertible<To>(from);
    }

@@ -57,7 +69,10 @@ template<typename To, typename From>
    constexpr void
    verify_nothrow_constructible(From from)
    {
-    static_assert(std::is_nothrow_constructible_v<To, From>);
+    if constexpr (is_layout_leftpad<typename To::layout_type>)
+      static_assert(std::is_constructible_v<To, From>);
+    else
+      static_assert(std::is_nothrow_constructible_v<To, From>);
      verify_constructible<To>(from);
    }

@@ -196,6 +211,16 @@ namespace from_extents
  // ctor: mapping(mapping<OExtents>)
  namespace from_same_layout
  {
+  template<typename Layout, typename Extents, typename OExtents>
+    constexpr void
+    verify_convertible(OExtents exts)
+    {
+      using Mapping = typename Layout::mapping<Extents>;
+      using OMapping = typename Layout::mapping<OExtents>;
+
+      ::verify_convertible<Mapping>(OMapping(exts));
+    }
+
    template<typename Layout, typename Extents, typename OExtents>
      constexpr void
      verify_nothrow_convertible(OExtents exts)
@@ -223,8 +248,12 @@ namespace from_same_layout
        verify_nothrow_convertible<Layout, std::extents<unsigned int>>(
         std::extents<int>{});

-      verify_nothrow_constructible<Layout, std::extents<int>>(
-       std::extents<unsigned int>{});
+      if constexpr (!is_layout_leftpad<Layout>)
+       verify_nothrow_constructible<Layout, std::extents<int>>(
+         std::extents<unsigned int>{});
+      else
+       verify_convertible<Layout, std::extents<int>>(
+         std::extents<unsigned int>{});

        assert_not_constructible<
         typename Layout::mapping<std::extents<int>>,
@@ -234,8 +263,12 @@ namespace from_same_layout
         typename Layout::mapping<std::extents<int, 1>>,
         typename Layout::mapping<std::extents<int>>>();

-      verify_nothrow_constructible<Layout, std::extents<int, 1>>(
-       std::extents<int, dyn>{1});
+      if constexpr (!is_layout_leftpad<Layout>)
+       verify_nothrow_constructible<Layout, std::extents<int, 1>>(
+         std::extents<int, dyn>{1});
+      else
+       verify_convertible<Layout, std::extents<int, 1>>(
+         std::extents<int, dyn>{1});

        verify_nothrow_convertible<Layout, std::extents<int, dyn>>(
         std::extents<int, 1>{});
@@ -247,8 +280,12 @@ namespace from_same_layout
        verify_nothrow_constructible<Layout, std::extents<int, 1, 2>>(
         std::extents<int, dyn, 2>{1});

-      verify_nothrow_convertible<Layout, std::extents<int, dyn, 2>>(
-       std::extents<int, 1, 2>{});
+      if constexpr (!is_layout_leftpad<Layout>)
+       verify_nothrow_convertible<Layout, std::extents<int, dyn, 2>>(
+         std::extents<int, 1, 2>{});
+      else
+       verify_nothrow_constructible<Layout, std::extents<int, dyn, 2>>(
+         std::extents<int, 1, 2>{});
        return true;
      }

@@ -429,6 +466,12 @@ main()
  {
    test_all<std::layout_left>();
    test_all<std::layout_right>();
+#ifdef __glibcxx_submdspan
+  test_all<std::layout_left_padded<0>>();
+  test_all<std::layout_left_padded<1>>();
+  test_all<std::layout_left_padded<2>>();
+  test_all<std::layout_left_padded<dyn>>();
+#endif

    from_left_or_right::test_all<std::layout_left, std::layout_right>();
    from_left_or_right::test_all<std::layout_right, std::layout_left>();
diff --git
a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc
new file mode 100644
index 00000000000..d4f9003fba6
--- /dev/null
+++
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc
@@ -0,0 +1,3 @@
+// { dg-do compile { target c++26 } }
+// { dg-require-debug-mode "" }
+#include<mdspan>
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc
index 655b9b6d6c3..d1aff172b64 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc
@@ -35,7 +35,8 @@ test_static_overflow()
  {
    constexpr Int n1 = std::numeric_limits<Int>::max();
    constexpr size_t n2 = std::dynamic_extent - 1;
-  constexpr size_t n = std::cmp_less(n1, n2) ? size_t(n1) : n2;
+  // Allow some room for padding.
+  constexpr size_t n = (std::cmp_less(n1, n2) ? size_t(n1) : n2) - 4;

    verify_all(typename Layout::mapping<std::extents<Int, n, n, 0, n,
n>>{});
    verify_all(typename Layout::mapping<std::extents<Int, 0, n, n, n>>{});
@@ -73,7 +74,8 @@ test_dynamic_overflow()
  {
    constexpr Int n1 = std::numeric_limits<Int>::max();
    constexpr size_t n2 = std::dynamic_extent - 1;
-  constexpr Int n = std::cmp_less(n1, n2) ? n1 : Int(n2);
+  // Allow some room for padding.
+  constexpr Int n = (std::cmp_less(n1, n2) ? n1 : Int(n2)) - 4;

    verify_all(make_mapping<Layout>(
        std::extents<Int, dyn, dyn, 0, dyn, dyn>{n, n, n, n}));
@@ -127,5 +129,9 @@ main()
    static_assert(test_all<std::layout_left>());
    static_assert(test_all<std::layout_right>());
    static_assert(test_all<std::layout_stride>());
+#ifdef __glibcxx_submdspan
+  static_assert(test_all<std::layout_left_padded<1>>());
+  static_assert(test_all<std::layout_left_padded<2>>());
+#endif
    return 0;
  }
diff --git
a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc
index 58bce514435..3b9719eaf5d 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc
@@ -7,6 +7,15 @@

  constexpr size_t dyn = std::dynamic_extent;

+template<typename Mapping>
+  concept has_static_is_exhaustive = requires
+  {
+    { Mapping::is_exhaustive() } -> std::same_as<bool>;
+  };
+

+static_assert(has_static_is_exhaustive<std::layout_right::mapping<std::extents<int>>>);

+static_assert(!has_static_is_exhaustive<std::layout_stride::mapping<std::extents<int>>>);
+
  template<typename Layout, typename Extents>
    constexpr bool
    test_mapping_properties()
@@ -32,7 +41,7 @@ template<typename Layout, typename Extents>

      static_assert(M::is_always_unique() && M::is_unique());
      static_assert(M::is_always_strided() && M::is_strided());
-    if constexpr (!std::is_same_v<Layout, std::layout_stride>)
+    if constexpr (has_static_is_exhaustive<M>)
        static_assert(M::is_always_exhaustive() && M::is_exhaustive());
      return true;
    }
@@ -306,7 +315,7 @@ template<typename Layout>
    constexpr void
    test_stride_1d()
    {
-    std::layout_left::mapping<std::extents<int, 3>> m;
+    typename Layout::mapping<std::extents<int, 3>> m;
      VERIFY(m.stride(0) == 1);
    }

@@ -321,73 +330,150 @@ template<>
    }

  template<typename Layout>
-  constexpr void
-  test_stride_2d();
+struct TestStride2D;

  template<>
-  constexpr void
-  test_stride_2d<std::layout_left>()
+  struct TestStride2D<std::layout_left>
    {
-    std::layout_left::mapping<std::extents<int, 3, 5>> m;
-    VERIFY(m.stride(0) == 1);
-    VERIFY(m.stride(1) == 3);
-  }
+    static constexpr void
+    run()
+    {
+      std::layout_left::mapping<std::extents<int, 3, 5>> m;
+      VERIFY(m.stride(0) == 1);
+      VERIFY(m.stride(1) == 3);
+    }
+  };

  template<>
-  constexpr void
-  test_stride_2d<std::layout_right>()
+  struct TestStride2D<std::layout_right>
    {
-    std::layout_right::mapping<std::extents<int, 3, 5>> m;
-    VERIFY(m.stride(0) == 5);
-    VERIFY(m.stride(1) == 1);
-  }
+    static constexpr void
+    run()
+    {
+      std::layout_right::mapping<std::extents<int, 3, 5>> m;
+      VERIFY(m.stride(0) == 5);
+      VERIFY(m.stride(1) == 1);
+    }
+  };

  template<>
+  struct TestStride2D<std::layout_stride>
+  {
+    static constexpr void
+    run()
+    {
+      std::array<int, 2> strides{13, 2};
+      std::layout_stride::mapping m(std::extents<int, 3, 5>{}, strides);
+      VERIFY(m.stride(0) == strides[0]);
+      VERIFY(m.stride(1) == strides[1]);
+      VERIFY(m.strides() == strides);
+    }
+  };
+
+#if __glibcxx_submdspan
+template<size_t PaddingValue>
+  struct TestStride2D<std::layout_left_padded<PaddingValue>>
+  {
+    static constexpr void
+    run()
+    {
+      using Layout = std::layout_left_padded<PaddingValue>;
+      typename Layout::mapping<std::extents<int, 3, 5>> m;
+
+      VERIFY(m.stride(0) == 1);
+
+      // The next multiple of PaddingValue, that's greater or equal
+      // to exts.extent(0) is the unique value in the range:
+      //   [exts.extent(0), exts.extent(0) + PaddingValue)
+      // that is divisible by PaddingValue.
+      VERIFY((m.stride(1) % PaddingValue) == 0);
+      VERIFY(3 <= m.stride(1) && std::cmp_less(m.stride(1), 3 +
PaddingValue));
+    }
+  };
+#endif
+
+template<typename Layout>
    constexpr void
-  test_stride_2d<std::layout_stride>()
+  test_stride_2d()
    {
-    std::array<int, 2> strides{13, 2};
-    std::layout_stride::mapping m(std::extents<int, 3, 5>{}, strides);
-    VERIFY(m.stride(0) == strides[0]);
-    VERIFY(m.stride(1) == strides[1]);
-    VERIFY(m.strides() == strides);
+    TestStride2D<Layout>::run();
    }

  template<typename Layout>
-  constexpr void
-  test_stride_3d();
+struct TestStride3D;

  template<>
-  constexpr void
-  test_stride_3d<std::layout_left>()
+  struct TestStride3D<std::layout_left>
    {
-    std::layout_left::mapping m(std::dextents<int, 3>(3, 5, 7));
-    VERIFY(m.stride(0) == 1);
-    VERIFY(m.stride(1) == 3);
-    VERIFY(m.stride(2) == 3*5);
-  }
+    static constexpr void
+    run()
+    {
+      std::layout_left::mapping m(std::dextents<int, 3>(3, 5, 7));
+      VERIFY(m.stride(0) == 1);
+      VERIFY(m.stride(1) == 3);
+      VERIFY(m.stride(2) == 3*5);
+    }
+  };
+

  template<>
-  constexpr void
-  test_stride_3d<std::layout_right>()
+  struct TestStride3D<std::layout_right>
    {
-    std::layout_right::mapping m(std::dextents<int, 3>(3, 5, 7));
-    VERIFY(m.stride(0) == 5*7);
-    VERIFY(m.stride(1) == 7);
-    VERIFY(m.stride(2) == 1);
-  }
+    static constexpr void
+    run()
+    {
+      std::layout_right::mapping m(std::dextents<int, 3>(3, 5, 7));
+      VERIFY(m.stride(0) == 5*7);
+      VERIFY(m.stride(1) == 7);
+      VERIFY(m.stride(2) == 1);
+    }
+  };

  template<>
+  struct TestStride3D<std::layout_stride>
+  {
+    static constexpr void
+    run()
+    {
+      std::dextents<int, 3> exts(3, 5, 7);
+      std::array<int, 3> strides{11, 2, 41};
+      std::layout_stride::mapping<std::dextents<int, 3>> m(exts, strides);
+      VERIFY(m.stride(0) == strides[0]);
+      VERIFY(m.stride(1) == strides[1]);
+      VERIFY(m.stride(2) == strides[2]);
+      VERIFY(m.strides() == strides);
+    }
+  };
+
+#if __glibcxx_submdspan
+template<size_t PaddingValue>
+  struct TestStride3D<std::layout_left_padded<PaddingValue>>
+  {
+    static constexpr void
+    run()
+    {
+      using Layout = std::layout_left_padded<PaddingValue>;
+      typename Layout::mapping<std::extents<int, 3, 5, 7>> m;
+
+      VERIFY(m.stride(0) == 1);
+
+      // The next multiple of PaddingValue, that's greater or equal
+      // to exts.extent(0) is the unique value in the range:
+      //   [exts.extent(0), exts.extent(0) + PaddingValue)
+      // that is divisible by PaddingValue.
+      VERIFY((m.stride(1) % PaddingValue) == 0);
+      VERIFY(3 <= m.stride(1) && std::cmp_less(m.stride(1), 3 +
PaddingValue));
+
+      VERIFY(m.stride(1) * 5 == m.stride(2));
+    }
+  };
+#endif
+
+template<typename Layout>
    constexpr void
-  test_stride_3d<std::layout_stride>()
+  test_stride_3d()
    {
-    std::dextents<int, 3> exts(3, 5, 7);
-    std::array<int, 3> strides{11, 2, 41};
-    std::layout_stride::mapping<std::dextents<int, 3>> m(exts, strides);
-    VERIFY(m.stride(0) == strides[0]);
-    VERIFY(m.stride(1) == strides[1]);
-    VERIFY(m.stride(2) == strides[2]);
-    VERIFY(m.strides() == strides);
+    TestStride3D<Layout>::run();
    }

  template<typename Layout>
@@ -411,7 +497,8 @@ template<typename Layout>
    test_has_stride_0d()
    {
      using Mapping = typename Layout::mapping<std::extents<int>>;
-    constexpr bool expected = std::is_same_v<Layout, std::layout_stride>;
+    constexpr bool expected = !(std::is_same_v<Layout, std::layout_left>
+       || std::is_same_v<Layout, std::layout_right>);
      static_assert(has_stride<Mapping> == expected);
    }

@@ -561,10 +648,27 @@ main()
    test_all<std::layout_left>();
    test_all<std::layout_right>();
    test_all<std::layout_stride>();
+#ifdef __glibcxx_submdspan
+  test_all<std::layout_left_padded<1>>();
+  test_all<std::layout_left_padded<2>>();
+  test_all<std::layout_left_padded<5>>();
+#endif

    test_has_op_eq<std::layout_right, std::layout_left, false>();
    test_has_op_eq<std::layout_right, std::layout_stride, true>();
    test_has_op_eq<std::layout_left, std::layout_stride, true>();
+#ifdef __glibcxx_submdspan
+  test_has_op_eq<std::layout_left, std::layout_left_padded<0>, false>();
+  test_has_op_eq<std::layout_left, std::layout_left_padded<6>, false>();
+  test_has_op_eq<std::layout_left, std::layout_left_padded<dyn>, false>();
+  // REVIEW: The next one looks strange, because it's neither. Somehow,
the
+  // conversion rules seem to be playing a critical role again.
+  // test_has_op_eq<std::layout_right, std::layout_left_padded<0>,
false>();
+
+  test_has_op_eq<std::layout_left_padded<2>, std::layout_left_padded<6>,
true>();
+  test_has_op_eq<std::layout_left_padded<2>,
std::layout_left_padded<dyn>, true>();
+#endif
+
    test_has_op_eq_peculiar();
    return 0;
  }
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
new file mode 100644
index 00000000000..6e79e191ff7
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
@@ -0,0 +1,537 @@
+// { dg-do run { target c++26 } }
+#include <mdspan>
+
+#include <cstdint>
+#include "../int_like.h"
+#include <testsuite_hooks.h>
+
+constexpr size_t dyn = std::dynamic_extent;
+
+constexpr bool
+test_default_ctor()
+{
+  using Extents = std::extents<size_t, 3, 5>;
+
+  std::layout_left_padded<2>::mapping<Extents> msta;
+  VERIFY(msta.stride(0) == 1);
+  VERIFY(msta.stride(1) == 4);
+
+  std::layout_left_padded<dyn>::mapping<Extents> mdyn;
+  VERIFY(mdyn.stride(0) == 1);
+  VERIFY(mdyn.stride(1) == 3);
+  return true;
+}
+
+constexpr bool
+test_from_exts()
+{
+  auto exts = std::dextents<size_t, 2>{3, 5};
+
+  std::layout_left_padded<0>::mapping m0_sta(exts);
+  VERIFY(m0_sta.stride(1) == exts.extent(0));
+
+  std::layout_left_padded<1>::mapping m1_sta(exts);
+  VERIFY(m1_sta.stride(1) == exts.extent(0));
+
+  std::layout_left_padded<2>::mapping m2_sta(exts);
+  VERIFY(m2_sta.stride(1) == 4);
+
+  std::layout_left_padded<dyn>::mapping mdyn(exts);
+  VERIFY(mdyn.stride(1) == exts.extent(0));
+  return true;
+}
+
+template<size_t PaddingValue, typename CustomPadType>
+constexpr bool
+test_from_pad()
+{
+  using Layout = std::layout_left_padded<PaddingValue>;
+  auto pad = 3;
+  auto exts = std::dextents<size_t, 3>{pad + 1, 5, 7};
+  typename Layout::mapping m(exts, CustomPadType{pad});
+  VERIFY(std::cmp_equal(m.stride(1), 2*pad));
+  VERIFY(m.extents() == exts);
+  return true;
+}
+
+template<size_t PaddingValue>
+constexpr void
+test_from_pad_all()
+{
+  test_from_pad<PaddingValue, int>();
+  static_assert(test_from_pad<PaddingValue, int>());
+
+  test_from_pad<PaddingValue, IntLike>();
+  test_from_pad<PaddingValue, MutatingInt>();
+  test_from_pad<PaddingValue, RValueInt>();
+
+  using Layout = std::layout_left_padded<PaddingValue>;
+  using Extents = std::dims<3>;
+  using Mapping = Layout::template mapping<Extents>;
+  static_assert(!std::is_constructible_v<Mapping, Extents, ThrowingInt>);
+  static_assert(!std::is_constructible_v<Mapping, Extents, NotIntLike>);
+}
+
+constexpr bool
+is_same_mapping(const auto& lhs, const auto& rhs)
+{
+  if (lhs.extents().rank() != rhs.extents().rank())
+    return false;
+
+  if (lhs.extents() != rhs.extents())
+    return false;
+
+  for (size_t i = 0; i < lhs.extents().rank(); ++i)
+    if (lhs.stride(i) != rhs.stride(i))
+      return false;
+  return true;
+}
+
+enum class ConversionRule
+{
+  Never,
+  Always,
+  Conventional
+};
+
+template<typename To, typename From>
+consteval bool
+should_convert(auto rule)
+{
+  if constexpr (rule == ConversionRule::Never)
+    return false;
+  if constexpr (rule == ConversionRule::Always)
+    return true;
+  else
+    return std::is_convertible_v<typename From::extents_type,
+                                typename To::extents_type>;
+}
+
+template<typename To, typename From>
+  constexpr void
+  check_convertible(const From& m, auto conversion_rule)
+  {
+    VERIFY(is_same_mapping(m, To(m)));
+    constexpr bool expected = should_convert<To, From>(conversion_rule);
+    static_assert(std::is_convertible_v<From, To> == expected);
+  }
+
+template<typename LayoutTo, typename Esta, typename Edyn, typename Ewrong>
+constexpr void
+check_convertible_variants(auto msta, auto conversion_rule)
+{
+  using LayoutFrom = decltype(msta)::layout_type;
+  constexpr auto cregular = std::cw<ConversionRule::Conventional>;
+
+  // There's a twist when both mappings are left-padded. There's two
distinct
+  // ctors: a defaulted copy ctor and a constrained template that enables
+  // construction from left-padded mappings even if their layout_type is
+  // different. The two ctors have different rules regarding conversion.
+
+  if constexpr (!std::same_as<LayoutTo, LayoutFrom>)
+    check_convertible<typename LayoutTo::mapping<Esta>>(msta,
conversion_rule);
+  else
+    check_convertible<typename LayoutTo::mapping<Esta>>(msta, cregular);
+
+  check_convertible<typename LayoutTo::mapping<Edyn>>(msta,
conversion_rule);
+
+  auto mdyn = typename LayoutFrom::mapping<Edyn>(msta);
+  check_convertible<typename LayoutTo::mapping<Esta>>(mdyn,
conversion_rule);
+  if constexpr (!std::same_as<LayoutTo, LayoutFrom>)
+    check_convertible<typename LayoutTo::mapping<Edyn>>(mdyn,
conversion_rule);
+  else
+    check_convertible<typename LayoutTo::mapping<Edyn>>(mdyn, cregular);
+
+  static_assert(!std::is_constructible_v<
+      typename LayoutTo::mapping<Esta>, typename
LayoutFrom::mapping<Ewrong>>);
+};
+
+template<typename PaddedLayout>
+  constexpr void
+  test_from_left_1d()
+  {
+    using E1 = std::extents<int, 6>;
+    using E2 = std::extents<int, dyn>;
+    using E3 = std::extents<int, 5>;
+    constexpr auto cr = std::cw<ConversionRule::Conventional>;
+
+    auto msta = std::layout_left::mapping(E1{});
+    check_convertible_variants<PaddedLayout, E1, E2, E3>(msta, cr);
+  }
+
+template<typename PaddedLayout>
+  constexpr void
+  test_from_left_2d()
+  {
+    using E1 = std::extents<int, 6, 5>;
+    using E2 = std::extents<int, dyn, 5>;
+    using E3 = std::extents<int, 6, 6>;
+    constexpr auto cr = std::cw<ConversionRule::Conventional>;
+
+    auto msta = std::layout_left::mapping(E1{});
+    check_convertible_variants<PaddedLayout, E1, E2, E3>(msta, cr);
+  }
+
+constexpr bool
+test_from_left()
+{
+  auto check = []<typename PaddedLayout>(PaddedLayout)
+  {
+    test_from_left_1d<PaddedLayout>();
+    test_from_left_2d<PaddedLayout>();
+  };
+
+  check(std::layout_left_padded<0>{});
+  check(std::layout_left_padded<1>{});
+  check(std::layout_left_padded<2>{});
+  check(std::layout_left_padded<6>{});
+  check(std::layout_left_padded<dyn>{});
+
+  // rank == 1 is more permissive:
+  test_from_left_1d<std::layout_left_padded<5>>();
+  return true;
+}
+
+constexpr bool
+test_from_stride()
+{
+  using E1 = std::extents<size_t, 6, 5, 7>;
+  using E2 = std::dims<3>;
+  using E3 = std::extents<size_t, 6, 6, 7>;
+
+  auto check = []<typename PaddedLayout>(PaddedLayout)
+  {
+    auto exts = E1{};
+    auto strides = std::array<int, 3>{
+      1, exts.extent(0), exts.extent(0) * exts.extent(1)};
+    constexpr auto cr = std::cw<ConversionRule::Never>;
+
+    auto m = std::layout_stride::mapping(exts, strides);
+    check_convertible_variants<PaddedLayout, E1, E2, E3>(m, cr);
+  };
+
+  check(std::layout_left_padded<0>{});
+  check(std::layout_left_padded<1>{});
+  check(std::layout_left_padded<2>{});
+  check(std::layout_left_padded<6>{});
+  check(std::layout_left_padded<dyn>{});
+  return true;
+}
+
+constexpr bool
+test_from_leftpad_0d()
+{
+  using E1 = std::extents<uint16_t>;
+  using E2 = std::extents<uint8_t>;
+  using E3 = std::extents<uint8_t, 1>;
+
+  std::layout_left_padded<6>::mapping<E1> msta{E1{}};
+
+  auto check = []<typename To>(To, auto m)
+  {
+    constexpr auto cr = std::cw<ConversionRule::Always>;
+    check_convertible_variants<To, E1, E2, E3>(m, cr);
+  };
+
+  check(std::layout_left_padded<6>{}, msta);
+  check(std::layout_left_padded<dyn>{}, msta);
+  return true;
+}
+
+constexpr bool
+test_from_leftpad_1d()
+{
+  using E1 = std::extents<int, 6>;
+  using E2 = std::extents<int, dyn>;
+  using E3 = std::extents<int, 6, 6>;
+
+  std::layout_left_padded<6>::mapping<E1> msta{E1{}};
+  std::layout_left_padded<dyn>::mapping<E1> mdyn{E1{}};
+
+  auto check = []<typename To>(To, auto m)
+  {
+    constexpr auto cr = std::cw<ConversionRule::Always>;
+    check_convertible_variants<To, E1, E2, E3>(m, cr);
+  };
+
+  // Remember, for rank <= 1 the padding_value is irrelevant.
+  check(std::layout_left_padded<6>{}, msta);
+  check(std::layout_left_padded<6>{}, mdyn);
+  check(std::layout_left_padded<dyn>{}, msta);
+  check(std::layout_left_padded<dyn>{}, mdyn);
+  return true;
+}
+
+constexpr bool
+test_from_leftpad_2d()
+{
+  using E1 = std::extents<int, 6, 5>;
+  using E2 = std::extents<int, dyn, 5>;
+  using E3 = std::extents<int, 6, 6>;
+
+  std::layout_left_padded<6>::mapping<E1> msta{E1{}};
+  std::layout_left_padded<dyn>::mapping<E1> mdyn{E1{}};
+
+  constexpr auto calways = std::cw<ConversionRule::Always>;
+  constexpr auto cnever = std::cw<ConversionRule::Never>;
+
+  auto check = []<typename To>(To, auto m, auto cr)
+  { check_convertible_variants<To, E1, E2, E3>(m, cr); };
+
+  check(std::layout_left_padded<6>{}, msta, cnever);
+  check(std::layout_left_padded<6>{}, mdyn, cnever);
+  check(std::layout_left_padded<dyn>{}, msta, calways);
+  check(std::layout_left_padded<dyn>{}, mdyn, cnever);
+  return true;
+}
+
+constexpr bool
+test_from_right()
+{
+  using E1 = std::extents<size_t, 3>;
+  using E2 = std::dims<1>;
+  using E3 = std::extents<size_t, 5>;
+  constexpr auto cr = std::cw<ConversionRule::Conventional>;
+
+  auto msta = std::layout_right::mapping(E1{});
+
+  // Remember, the padding_value has no effect for rank <= 1.
+  check_convertible_variants<std::layout_left_padded<0>, E1, E2,
E3>(msta, cr);
+  check_convertible_variants<std::layout_left_padded<1>, E1, E2,
E3>(msta, cr);
+  check_convertible_variants<std::layout_left_padded<2>, E1, E2,
E3>(msta, cr);
+  check_convertible_variants<std::layout_left_padded<5>, E1, E2,
E3>(msta, cr);
+  check_convertible_variants<std::layout_left_padded<6>, E1, E2,
E3>(msta, cr);
+  check_convertible_variants<std::layout_left_padded<dyn>, E1, E2,
E3>(msta, cr);
+  return true;
+}
+
+constexpr bool
+test_to_left()
+{
+  using E1 = std::extents<int, 6, 5>;
+  using E2 = std::extents<int, dyn, 5>;
+  using E3 = std::extents<int, 6, 6>;
+
+  auto exts_sta = E1{};
+
+  auto check = [](auto msta)
+  {
+    constexpr auto cr = std::cw<ConversionRule::Conventional>;
+    check_convertible_variants<std::layout_left, E1, E2, E3>(msta, cr);
+  };
+
+  check(std::layout_left_padded<0>::mapping(exts_sta));
+  check(std::layout_left_padded<2>::mapping(exts_sta));
+  check(std::layout_left_padded<6>::mapping(exts_sta));
+  check(std::layout_left_padded<dyn>::mapping(exts_sta, 0));
+  check(std::layout_left_padded<dyn>::mapping(exts_sta, 2));
+  check(std::layout_left_padded<dyn>::mapping(exts_sta, 6));
+  return true;
+}
+
+constexpr bool
+test_never_to_right()
+{
+  using E1 = std::extents<size_t, 3>;
+  using E2 = std::dims<1>;
+
+  auto mr_sta = std::layout_right::mapping(E1{});
+  auto mlp_sta = std::layout_left_padded<2>::mapping<E1>{mr_sta};
+  static_assert(!std::is_constructible_v<decltype(mr_sta),
decltype(mlp_sta)>);
+
+  auto mr_dyn = std::layout_right::mapping(E2{E1{}});
+  auto mlp_dyn = std::layout_left_padded<2>::mapping<E2>{mr_dyn};
+  static_assert(!std::is_constructible_v<decltype(mr_dyn),
decltype(mlp_dyn)>);
+  return true;
+}
+
+template<typename Layout>
+constexpr void
+test_strides()
+{
+  auto check = [](auto exts)
+  {
+    auto m = typename Layout::mapping(exts);
+    using IndexType = typename decltype(m)::index_type;
+    constexpr size_t rank = decltype(m)::extents_type::rank();
+
+    auto strides = m.strides();
+    static_assert(std::same_as<decltype(strides), std::array<IndexType,
rank>>);
+    VERIFY(strides.size() == rank);
+    for (size_t i = 0; i < strides.size(); ++i)
+      VERIFY(strides[i] == m.stride(i));
+  };
+
+  check(std::extents());
+  check(std::extents(0));
+  check(std::extents(3));
+  check(std::extents(3, 5, 7));
+  check(std::extents(3, 0, 7));
+}
+
+constexpr bool
+test_strides_all()
+{
+  test_strides<std::layout_left_padded<0>>();
+  test_strides<std::layout_left_padded<1>>();
+  test_strides<std::layout_left_padded<3>>();
+  test_strides<std::layout_left_padded<dyn>>();
+  return true;
+}
+
+constexpr void
+test_exhaustive_0d()
+{
+  auto exts = std::extents<int>{};
+
+  auto check = [](auto m)
+  {
+    static_assert(m.is_always_exhaustive());
+    VERIFY(m.is_exhaustive());
+  };
+
+  check(std::layout_left_padded<0>::mapping(exts));
+  check(std::layout_left_padded<1>::mapping(exts));
+  check(std::layout_left_padded<2>::mapping(exts));
+  check(std::layout_left_padded<dyn>::mapping(exts));
+}
+
+constexpr void
+test_exhaustive_1d()
+{
+  auto check_dyn_and_sta = []<typename Layout>(Layout)
+  {
+    auto check = [](auto exts)
+    {
+      auto m = typename Layout::mapping(exts);
+      static_assert(m.is_always_exhaustive());
+      VERIFY(m.is_exhaustive());
+    };
+
+    check(std::extents(4));
+    check(std::extents<int, 4>{});
+  };
+
+  check_dyn_and_sta(std::layout_left_padded<1>{});
+  check_dyn_and_sta(std::layout_left_padded<2>{});
+  check_dyn_and_sta(std::layout_left_padded<6>{});
+  check_dyn_and_sta(std::layout_left_padded<dyn>{});
+}
+
+constexpr void
+test_exhaustive_3d()
+{
+  auto exts_dyn = std::extents(4, 5, 7);
+  auto exts_sta = std::extents<int, 4, 5, 7>{};
+  auto ctrue = std::cw<true>;
+  auto cfalse= std::cw<false>;
+
+  auto check = [](auto m, auto static_expected, auto runtime_expected)
+  {
+    static_assert(m.is_always_exhaustive() == static_expected);
+    VERIFY(m.is_exhaustive() == runtime_expected);
+  };
+
+  check(std::layout_left_padded<0>::mapping(exts_sta), ctrue, true);
+  check(std::layout_left_padded<0>::mapping(exts_dyn), cfalse, true);
+  check(std::layout_left_padded<1>::mapping(exts_sta), ctrue, true);
+  check(std::layout_left_padded<1>::mapping(exts_dyn), cfalse, true);
+  check(std::layout_left_padded<2>::mapping(exts_sta), ctrue, true);
+  check(std::layout_left_padded<2>::mapping(exts_dyn), cfalse, true);
+  check(std::layout_left_padded<6>::mapping(exts_dyn), cfalse, false);
+  check(std::layout_left_padded<6>::mapping(exts_sta), cfalse, false);
+  check(std::layout_left_padded<dyn>::mapping(exts_sta), cfalse, true);
+  check(std::layout_left_padded<dyn>::mapping(exts_dyn, 2), cfalse, true);
+  check(std::layout_left_padded<dyn>::mapping(exts_dyn, 3), cfalse,
false);
+}
+
+constexpr bool
+test_exhaustive()
+{
+  test_exhaustive_0d();
+  test_exhaustive_1d();
+  test_exhaustive_3d();
+  return true;
+}
+
+constexpr bool
+test_op_eq()
+{
+  // The generic cases are handled in layouts/mapping.cc. Here we check
special
+  // cases related to non exhaustive layouts.
+  using E1 = std::extents<size_t, 6, 5, 7>;
+  using E2 = std::dims<3>;
+  using E3 = std::extents<size_t, 7, 5, 7>;
+
+  auto m1 = std::layout_left_padded<0>::mapping(E1{});
+  auto m2 = std::layout_left_padded<7>::mapping(E1{});
+
+  VERIFY(m1 == std::layout_left_padded<0>::mapping(E1{}));
+  VERIFY(m1 == std::layout_left_padded<1>::mapping(E1{}));
+  VERIFY(m1 == std::layout_left_padded<2>::mapping(E1{}));
+  VERIFY(m1 == std::layout_left_padded<6>::mapping(E1{}));
+  VERIFY(m1 != std::layout_left_padded<7>::mapping(E1{}));
+  VERIFY(m1 == std::layout_left_padded<dyn>::mapping(E1{}));
+  VERIFY(m1 != std::layout_left_padded<dyn>::mapping(E1{}, 7));
+
+  VERIFY(m1 == std::layout_left_padded<0>::mapping(E2{E1{}}));
+  VERIFY(m1 == std::layout_left_padded<1>::mapping(E2{E1{}}));
+  VERIFY(m1 == std::layout_left_padded<2>::mapping(E2{E1{}}));
+  VERIFY(m1 == std::layout_left_padded<6>::mapping(E2{E1{}}));
+  VERIFY(m1 != std::layout_left_padded<7>::mapping(E2{E1{}}));
+  VERIFY(m1 == std::layout_left_padded<dyn>::mapping(E2{E1{}}));
+  VERIFY(m1 != std::layout_left_padded<dyn>::mapping(E2{E1{}}, 7));
+
+  VERIFY(m2 == std::layout_left_padded<7>::mapping(E1{}));
+  VERIFY(m2 == std::layout_left_padded<dyn>::mapping(E1{}, 7));
+  VERIFY(m2 == std::layout_left_padded<7>::mapping(E2{E1{}}));
+  VERIFY(m2 == std::layout_left_padded<dyn>::mapping(E2{E1{}}, 7));
+
+  VERIFY(m2 != std::layout_left_padded<7>::mapping(E3{}));
+  VERIFY(m2 != std::layout_left_padded<dyn>::mapping(E3{}, 7));
+  return true;
+}
+
+int
+main()
+{
+  test_default_ctor();
+  static_assert(test_default_ctor());
+
+  test_from_exts();
+  static_assert(test_from_exts());
+
+  test_from_left();
+  static_assert(test_from_left());
+
+  test_from_pad_all<dyn>();
+  test_from_pad_all<3>();
+
+  test_from_stride();
+  static_assert(test_from_stride());
+
+  test_from_leftpad_0d();
+  static_assert(test_from_leftpad_0d());
+
+  test_from_leftpad_1d();
+  static_assert(test_from_leftpad_1d());
+
+  test_from_leftpad_2d();
+  static_assert(test_from_leftpad_2d());
+
+  test_from_right();
+  static_assert(test_from_right());
+
+  test_to_left();
+  static_assert(test_to_left());
+
+  test_never_to_right();
+  static_assert(test_never_to_right());
+
+  test_strides_all();
+  static_assert(test_strides_all());
+
+  test_exhaustive();
+  static_assert(test_exhaustive());
+  return 0;
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc
new file mode 100644
index 00000000000..83b4402d8c5
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc
@@ -0,0 +1,225 @@
+// { dg-do compile { target c++26 } }
+#include <mdspan>
+
+#include <cstdint>
+
+constexpr size_t dyn = std::dynamic_extent;
+
+constexpr bool
+test_from_extens_oversized()
+{
+  using E1 = std::dextents<uint16_t, 2>;
+  using E2 = std::dextents<uint8_t, 2>;
+
+  auto mlp = std::layout_left_padded<dyn>::mapping(E1{8, 128});
+  auto m = std::layout_left_padded<dyn>::mapping<E2>(mlp);
+  (void) m;
+  return true;
+}
+
+constexpr bool
+test_from_left()
+{
+  auto exts = std::extents<uint8_t, 6, dyn>{4};
+  auto ml = std::layout_left::mapping(exts);
+
+  using Layout = std::layout_left_padded<4>;
+  Layout::mapping<decltype(exts)> m(ml); // { dg-error "from here" }
+  return true;
+}
+static_assert(test_from_left());
+
+constexpr bool
+test_from_left_bad_runtime_stride()
+{
+  auto exts = std::extents<uint8_t, dyn, dyn>{6, 4};
+  auto ml = std::layout_left::mapping(exts);
+
+  using Layout = std::layout_left_padded<4>;
+  Layout::mapping<decltype(exts)> m(ml); // { dg-error "expansion of" }
+  return true;
+}
+static_assert(test_from_left_bad_runtime_stride());
+
+constexpr bool
+test_from_left_oversized_extents()
+{
+  auto exts = std::extents<uint16_t, dyn, dyn>{8, 128};
+  auto ml = std::layout_left::mapping(exts);
+
+  using Layout = std::layout_left_padded<8>;
+  Layout::mapping<std::extents<uint8_t, dyn, dyn>> m(ml); // { dg-error
"expansion of" }
+  return true;
+}
+static_assert(test_from_left_oversized_extents());
+
+template<size_t PaddingValue>
+  constexpr bool
+  test_pad_overflow()
+  {
+    auto exts = std::extents<uint8_t, dyn>{4};
+    auto n = size_t(1) << 9;
+    auto m = typename
std::layout_left_padded<PaddingValue>::mapping(exts, n);
+    (void) m;
+    return true;
+  }
+static_assert(test_pad_overflow<1>());   // { dg-error "expansion of" }
+static_assert(test_pad_overflow<dyn>()); // { dg-error "expansion of" }
+
+template<size_t PaddingValue>
+  constexpr bool
+  test_from_pad_negative()
+  {
+    auto exts = std::extents(4);
+    auto m = typename
std::layout_left_padded<PaddingValue>::mapping(exts, -1);
+    (void) m;
+    return true;
+  }
+static_assert(test_from_pad_negative<1>());   // { dg-error "expansion
of" }
+static_assert(test_from_pad_negative<dyn>()); // { dg-error "expansion
of" }
+
+template<size_t Pad>
+  constexpr bool
+  test_static_pad_same()
+  {
+    using Extents = std::extents<int, dyn>;
+    using Mapping = typename
std::layout_left_padded<Pad>::mapping<Extents>;
+    auto exts = Extents{4};
+    auto m = Mapping(exts, Pad + 1);      // { dg-error "expansion of" }
+    (void) m;
+    return true;
+  }
+static_assert(test_static_pad_same<1>()); // { dg-error "expansion of" }
+static_assert(test_static_pad_same<3>()); // { dg-error "expansion of" }
+
+constexpr bool
+test_from_stride_wrong_stride0()
+{
+  using Extents = std::dextents<size_t, 2>;
+  auto e = Extents{3, 5};
+  auto ms = std::layout_stride::mapping(e, std::array<int, 2>{2, 7});
+  auto m = std::layout_left_padded<dyn>::mapping<Extents>(ms); // {
dg-error "expansion of" }
+  (void) m;
+  return true;
+}
+static_assert(test_from_stride_wrong_stride0());
+
+constexpr bool
+test_from_stride_wrong_stride1()
+{
+  using Extents = std::dextents<size_t, 2>;
+  auto e = Extents{3, 5};
+  auto ms = std::layout_stride::mapping(e, std::array<int, 2>{1, 3});
+  auto m = std::layout_left_padded<2>::mapping<Extents>(ms); // {
dg-error "expansion of" }
+  (void) m;
+  return true;
+}
+static_assert(test_from_stride_wrong_stride1());
+
+constexpr bool
+test_from_stride_wrong_stride2()
+{
+  using Extents = std::dims<3>;
+  auto e = Extents{3, 5, 7};
+  auto ms = std::layout_stride::mapping(e, std::array<int, 3>{1, 4, 21});
+  auto m = std::layout_left_padded<dyn>::mapping<Extents>(ms); // here
(not implemented)
+  (void) m;
+  return true;
+}
+static_assert(test_from_stride_wrong_stride2());
+
+constexpr bool
+test_from_stride_oversized()
+{
+  auto exts = std::extents<uint16_t, dyn, dyn>{2, 6};
+  auto ms = std::layout_stride::mapping(exts, std::array<uint16_t, 2>{1,
128});
+
+  using Layout = std::layout_left_padded<dyn>;
+  Layout::mapping<std::extents<uint8_t, dyn, dyn>> m(ms); // { dg-error
"expansion of" }
+  (void) m;
+  return true;
+}
+static_assert(test_from_stride_oversized());
+
+constexpr bool
+test_from_leftpad_dyn()
+{
+  using Extents = std::dextents<size_t, 2>;
+  auto e = Extents{3, 5};
+  auto mlp = std::layout_left_padded<dyn>::mapping(e);
+  auto m = std::layout_left_padded<2>::mapping<Extents>(mlp); // {
dg-error "expansion of" }
+  (void) m;
+  return true;
+}
+static_assert(test_from_leftpad_dyn());
+
+constexpr bool
+test_from_leftpad_sta()
+{
+  using Extents = std::dextents<size_t, 2>;
+  auto e = Extents{3, 5};
+  auto mlp = std::layout_left_padded<3>::mapping(e);
+  auto m = std::layout_left_padded<2>::mapping<Extents>(mlp); // {
dg-error "required from" }
+  (void) m;
+  return true;
+}
+static_assert(test_from_leftpad_sta());
+
+constexpr bool
+test_from_leftpad_oversized()
+{
+  using E1 = std::extents<uint16_t, 8, 128>;
+  using E2 = std::extents<uint8_t, dyn, dyn>;
+  auto mlp = std::layout_left_padded<dyn>::mapping<E1>(E1{});
+  auto m = std::layout_left_padded<dyn>::mapping<E2>(mlp); // { dg-error
"expansion of" }
+  (void) m;
+  return true;
+}
+static_assert(test_from_leftpad_oversized());
+
+template<size_t RunId>
+  constexpr bool
+  test_to_left_not_exhaustive()
+  {
+    using E1 = std::extents<int, 6, 5>;
+    using E2 = std::extents<int, dyn, 5>;
+
+    [[maybe_unused]] auto msta =
std::layout_left_padded<7>::mapping(E1{});
+    if constexpr (RunId == 0)
+      {
+       auto m = std::layout_left::mapping<E1>(msta); // { dg-error
"required from" }
+       (void) m;
+      }
+    if constexpr (RunId == 1)
+      {
+       auto m = std::layout_left::mapping<E2>(msta); // { dg-error
"expansion of" }
+       (void) m;
+      }
+
+    [[maybe_unused]] auto mdyn =
std::layout_left_padded<dyn>::mapping(E2{E1{}}, 7);
+    if constexpr (RunId == 2)
+      {
+       auto m = std::layout_left::mapping<E1>(mdyn); // { dg-error
"expansion of" }
+       (void) m;
+      }
+    if constexpr (RunId == 3)
+      {
+       auto m = std::layout_left::mapping<E2>(mdyn); // { dg-error
"expansion of" }
+       (void) m;
+      }
+    return true;
+  }
+static_assert(test_to_left_not_exhaustive<0>());
+static_assert(test_to_left_not_exhaustive<1>());
+static_assert(test_to_left_not_exhaustive<2>());
+static_assert(test_to_left_not_exhaustive<3>());
+
+// { dg-prune-output "padding_value must be representable as index_type" }
+// { dg-prune-output "non-constant condition for static assertion" }
+// { dg-prune-output "called in a constant expression" }
+// { dg-prune-output "no matching function" }
+// { dg-prune-output "static assertion failed" }
+// { dg-prune-output "__glibcxx_assert_fail()" }
+// { dg-prune-output "must be compatible with other.stride" }
+// { dg-prune-output "padding_value is dynamic_extent" }
+// { dg-prune-output "_S_rank <= 1" }
--
2.50.1




Reply via email to