[libcxx-commits] [libcxx] [libc++] mdspan - implement layout_stride (PR #69650)

Christian Trott via libcxx-commits libcxx-commits at lists.llvm.org
Thu Oct 19 15:26:09 PDT 2023


https://github.com/crtrott created https://github.com/llvm/llvm-project/pull/69650

This implements layout_stride for C++23 and with that completes the implementation of the C++23 mdspan header. The feature test macro is added, and the status pages updated.

Co-authored-by: Damien L-G <dalg24 at gmail.com>

Differential Revision: https://reviews.llvm.org/D157171

>From f0b845826588f3051522d5a35154a5f5ab63583d Mon Sep 17 00:00:00 2001
From: Christian Trott <crtrott at sandia.gov>
Date: Thu, 19 Oct 2023 16:22:20 -0600
Subject: [PATCH] [libc++] mdspan - implement layout_stride

This implements layout_stride for C++23 and with that completes the implementation of the C++23 mdspan header.
The feature test macro is added, and the status pages updated.

Co-authored-by: Damien L-G <dalg24 at gmail.com>

Differential Revision: https://reviews.llvm.org/D157171
---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +-
 libcxx/docs/Status/Cxx23.rst                  |   1 -
 libcxx/docs/Status/Cxx23Papers.csv            |   8 +-
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__fwd/mdspan.h                 |   7 +-
 libcxx/include/__mdspan/layout_left.h         |  27 +-
 libcxx/include/__mdspan/layout_right.h        |  27 +-
 libcxx/include/__mdspan/layout_stride.h       | 366 ++++++++++++++++++
 libcxx/include/mdspan                         |  59 +++
 libcxx/include/module.modulemap.in            |   1 +
 libcxx/include/version                        |   2 +-
 libcxx/modules/std/mdspan.inc                 |   2 +-
 .../assert.ctor.layout_stride.pass.cpp        |  83 ++++
 .../assert.ctor.layout_stride.pass.cpp        |  83 ++++
 .../layout_stride/assert.conversion.pass.cpp  | 112 ++++++
 ...ert.ctor.extents_array.non_unique.pass.cpp |  67 ++++
 .../assert.ctor.extents_array.pass.cpp        |  73 ++++
 ...sert.ctor.extents_span.non_unique.pass.cpp |  70 ++++
 .../assert.ctor.extents_span.pass.cpp         |  80 ++++
 .../assert.index_operator.pass.cpp            |  88 +++++
 .../layout_stride/assert.stride.pass.cpp      |  36 ++
 .../mdspan/mdspan/assert.conversion.pass.cpp  |   2 +-
 .../views/mdspan/mdspan/assert.size.pass.cpp  |   2 +-
 .../test/libcxx/transitive_includes/cxx03.csv |   1 +
 .../test/libcxx/transitive_includes/cxx11.csv |   1 +
 .../test/libcxx/transitive_includes/cxx14.csv |   1 +
 .../test/libcxx/transitive_includes/cxx17.csv |   1 +
 .../test/libcxx/transitive_includes/cxx20.csv |   1 +
 .../test/libcxx/transitive_includes/cxx23.csv |   1 +
 .../test/libcxx/transitive_includes/cxx26.csv |   1 +
 .../mdspan/{mdspan => }/CustomTestLayouts.h   |  47 ++-
 .../layout_left/ctor.layout_stride.pass.cpp   | 114 ++++++
 .../layout_right/ctor.layout_stride.pass.cpp  | 114 ++++++
 .../mdspan/layout_stride/comparison.pass.cpp  | 198 ++++++++++
 .../layout_stride/ctor.default.pass.cpp       |  73 ++++
 .../layout_stride/ctor.extents_array.pass.cpp | 138 +++++++
 .../layout_stride/ctor.extents_span.pass.cpp  | 141 +++++++
 .../ctor.strided_mapping.pass.cpp             | 187 +++++++++
 .../mdspan/layout_stride/deduction.pass.cpp   |  55 +++
 .../mdspan/layout_stride/extents.verify.cpp   |  33 ++
 .../layout_stride/index_operator.pass.cpp     | 121 ++++++
 .../is_exhaustive_corner_case.pass.cpp        |  54 +++
 .../mdspan/layout_stride/properties.pass.cpp  | 116 ++++++
 .../layout_stride/required_span_size.pass.cpp |  56 +++
 .../static_requirements.pass.cpp              | 131 +++++++
 .../mdspan/layout_stride/stride.pass.cpp      |  57 +++
 .../views/mdspan/mdspan/assign.pass.cpp       |   2 +-
 .../views/mdspan/mdspan/conversion.pass.cpp   |   2 +-
 .../views/mdspan/mdspan/conversion.verify.cpp |   2 +-
 .../views/mdspan/mdspan/ctor.copy.pass.cpp    |   2 +-
 .../views/mdspan/mdspan/ctor.default.pass.cpp |   2 +-
 .../mdspan/mdspan/ctor.dh_array.pass.cpp      |   2 +-
 .../mdspan/mdspan/ctor.dh_extents.pass.cpp    |   2 +-
 .../mdspan/mdspan/ctor.dh_integers.pass.cpp   |   2 +-
 .../views/mdspan/mdspan/ctor.dh_map.pass.cpp  |   2 +-
 .../mdspan/mdspan/ctor.dh_map_acc.pass.cpp    |   2 +-
 .../views/mdspan/mdspan/ctor.dh_span.pass.cpp |   2 +-
 .../views/mdspan/mdspan/ctor.move.pass.cpp    |   2 +-
 .../views/mdspan/mdspan/deduction.pass.cpp    |   2 +-
 .../mdspan/mdspan/index_operator.pass.cpp     |   2 +-
 .../views/mdspan/mdspan/move.pass.cpp         |   2 +-
 .../views/mdspan/mdspan/properties.pass.cpp   |   2 +-
 .../views/mdspan/mdspan/swap.pass.cpp         |   2 +-
 .../views/mdspan/mdspan/types.pass.cpp        |   2 +-
 .../mdspan.version.compile.pass.cpp           |  32 +-
 .../version.version.compile.pass.cpp          |  32 +-
 .../generate_feature_test_macro_components.py |   1 -
 67 files changed, 2835 insertions(+), 107 deletions(-)
 create mode 100644 libcxx/include/__mdspan/layout_stride.h
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_left/assert.ctor.layout_stride.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_right/assert.ctor.layout_stride.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.conversion.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.non_unique.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.non_unique.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.index_operator.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.stride.pass.cpp
 rename libcxx/test/std/containers/views/mdspan/{mdspan => }/CustomTestLayouts.h (87%)
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_left/ctor.layout_stride.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_right/ctor.layout_stride.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/comparison.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/ctor.default.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_array.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_span.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/ctor.strided_mapping.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/deduction.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/extents.verify.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/index_operator.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/properties.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/required_span_size.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/static_requirements.pass.cpp
 create mode 100644 libcxx/test/std/containers/views/mdspan/layout_stride/stride.pass.cpp

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 2b21ec3d2fe23ac..10afd64e0a39469 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -332,7 +332,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_is_scoped_enum``                        ``202011L``
     --------------------------------------------------- -----------------
-    ``__cpp_lib_mdspan``                                *unimplemented*
+    ``__cpp_lib_mdspan``                                ``202207L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_move_only_function``                    *unimplemented*
     --------------------------------------------------- -----------------
diff --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst
index 5b4d9a6fe943d45..839640a7c7e881b 100644
--- a/libcxx/docs/Status/Cxx23.rst
+++ b/libcxx/docs/Status/Cxx23.rst
@@ -40,7 +40,6 @@ Paper Status
 
 .. note::
 
-   .. [#note-P0009R18] P0009R18: ``extents``, ``dextents``, ``layout_left``, ``layout_right``, and ``default_accessor`` are implemented.
    .. [#note-P0533R9] P0533R9: ``isfinite``, ``isinf``, ``isnan`` and ``isnormal`` are implemented.
    .. [#note-P1413R3] P1413R3: ``std::aligned_storage_t`` and ``std::aligned_union_t`` are marked deprecated, but
       clang doesn't issue a diagnostic for deprecated using template declarations.
diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index 35319fb7576d3c6..0513a4a6e9fe9b5 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -51,7 +51,7 @@
 "`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|"
 "`P2443R1 <https://wg21.link/P2443R1>`__","LWG","``views::chunk_by``","February 2022","|Complete|","18.0","|ranges|"
 "","","","","","",""
-"`P0009R18 <https://wg21.link/P0009R18>`__","LWG","mdspan: A Non-Owning Multidimensional Array Reference","July 2022","|In progress| [#note-P0009R18]_",""
+"`P0009R18 <https://wg21.link/P0009R18>`__","LWG","mdspan: A Non-Owning Multidimensional Array Reference","July 2022","|Complete|","18.0"
 "`P0429R9 <https://wg21.link/P0429R9>`__","LWG","A Standard ``flat_map``","July 2022","",""
 "`P1169R4 <https://wg21.link/P1169R4>`__","LWG","``static operator()``","July 2022","|Complete|","16.0"
 "`P1222R4 <https://wg21.link/P1222R4>`__","LWG","A Standard ``flat_set``","July 2022","",""
@@ -89,9 +89,9 @@
 "`P2549R1 <https://wg21.link/P2549R1>`__","LWG","``std::unexpected`` should have ``error()`` as member accessor","July 2022","|Complete|","16.0"
 "`P2585R0 <https://wg21.link/P2585R0>`__","LWG","Improving default container formatting","July 2022","|Complete|","17.0"
 "`P2590R2 <https://wg21.link/P2590R2>`__","LWG","Explicit lifetime management","July 2022","",""
-"`P2599R2 <https://wg21.link/P2599R2>`__","LWG","``mdspan::size_type`` should be ``index_type``","July 2022","",""
-"`P2604R0 <https://wg21.link/P2604R0>`__","LWG","mdspan: rename pointer and contiguous","July 2022","",""
-"`P2613R1 <https://wg21.link/P2613R1>`__","LWG","Add the missing ``empty`` to ``mdspan``","July 2022","",""
+"`P2599R2 <https://wg21.link/P2599R2>`__","LWG","``mdspan::size_type`` should be ``index_type``","July 2022","|Complete|","18.0"
+"`P2604R0 <https://wg21.link/P2604R0>`__","LWG","mdspan: rename pointer and contiguous","July 2022","|Complete|","18.0"
+"`P2613R1 <https://wg21.link/P2613R1>`__","LWG","Add the missing ``empty`` to ``mdspan``","July 2022","|Complete|","18.0"
 "","","","","","",""
 "`P1202R5 <https://wg21.link/P1202R5>`__","LWG", "Asymmetric Fences", "November 2022","","","|concurrency TS|"
 "`P1264R2 <https://wg21.link/P1264R2>`__","LWG", "Revising the wording of ``stream`` input operations", "November 2022","|Complete|","9.0",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 9b03430a87d8338..85470c5b337f39f 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -508,6 +508,7 @@ set(files
   __mdspan/extents.h
   __mdspan/layout_left.h
   __mdspan/layout_right.h
+  __mdspan/layout_stride.h
   __mdspan/mdspan.h
   __memory/addressof.h
   __memory/align.h
diff --git a/libcxx/include/__fwd/mdspan.h b/libcxx/include/__fwd/mdspan.h
index a3628c2d60dcc8f..8889567a047f6ec 100644
--- a/libcxx/include/__fwd/mdspan.h
+++ b/libcxx/include/__fwd/mdspan.h
@@ -42,14 +42,11 @@ struct layout_right {
   class mapping;
 };
 
-/*
-// Will be implemented with follow on revision
 // Layout policy with a unique mapping where strides are arbitrary
 struct layout_stride {
-  template<class Extents>
-    class mapping;
+  template <class _Extents>
+  class mapping;
 };
-*/
 
 #endif // _LIBCPP_STD_VER >= 23
 
diff --git a/libcxx/include/__mdspan/layout_left.h b/libcxx/include/__mdspan/layout_left.h
index 5faae597f6f81fd..fd644fa0c53226b 100644
--- a/libcxx/include/__mdspan/layout_left.h
+++ b/libcxx/include/__mdspan/layout_left.h
@@ -110,12 +110,27 @@ class layout_left::mapping {
         "layout_left::mapping converting ctor: other.required_span_size() must be representable as index_type.");
   }
 
-// FIXME: add when we add other layouts
-#  if 0
-    template<class _OtherExtents>
-      constexpr explicit(extents_type::rank() > 0)
-        mapping(const layout_stride::mapping_<OtherExtents>&) noexcept;
-#  endif
+  template <class _OtherExtents>
+    requires(is_constructible_v<extents_type, _OtherExtents>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit(extents_type::rank() > 0)
+      mapping(const layout_stride::mapping<_OtherExtents>& __other) noexcept
+      : __extents_(__other.extents()) {
+    if constexpr (extents_type::rank() > 0) {
+      _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+          ([&]() {
+            using _CommonType = common_type_t<typename extents_type::index_type, typename _OtherExtents::index_type>;
+            for (rank_type __r = 0; __r < extents_type::rank(); __r++)
+              if (static_cast<_CommonType>(stride(__r)) != static_cast<_CommonType>(__other.stride(__r)))
+                return false;
+            return true;
+          }()),
+          "layout_left::mapping from layout_stride ctor: strides are not compatible with layout_left.");
+      _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+          __mdspan_detail::__is_representable_as<index_type>(__other.required_span_size()),
+          "layout_left::mapping from layout_stride ctor: other.required_span_size() must be representable as "
+          "index_type.");
+    }
+  }
 
   _LIBCPP_HIDE_FROM_ABI constexpr mapping& operator=(const mapping&) noexcept = default;
 
diff --git a/libcxx/include/__mdspan/layout_right.h b/libcxx/include/__mdspan/layout_right.h
index 4f95789a2fafcc9..8e64d07dd52309c 100644
--- a/libcxx/include/__mdspan/layout_right.h
+++ b/libcxx/include/__mdspan/layout_right.h
@@ -109,12 +109,27 @@ class layout_right::mapping {
         "layout_right::mapping converting ctor: other.required_span_size() must be representable as index_type.");
   }
 
-// FIXME: add when we add other layouts
-#  if 0
-    template<class _OtherExtents>
-      constexpr explicit(extents_type::rank() > 0)
-        mapping(const layout_stride::mapping_<OtherExtents>&) noexcept;
-#  endif
+  template <class _OtherExtents>
+    requires(is_constructible_v<extents_type, _OtherExtents>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit(extents_type::rank() > 0)
+      mapping(const layout_stride::mapping<_OtherExtents>& __other) noexcept
+      : __extents_(__other.extents()) {
+    if constexpr (extents_type::rank() > 0) {
+      _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+          ([&]() {
+            using _CommonType = common_type_t<typename extents_type::index_type, typename _OtherExtents::index_type>;
+            for (rank_type __r = 0; __r < extents_type::rank(); __r++)
+              if (static_cast<_CommonType>(stride(__r)) != static_cast<_CommonType>(__other.stride(__r)))
+                return false;
+            return true;
+          }()),
+          "layout_right::mapping from layout_stride ctor: strides are not compatible with layout_right.");
+      _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+          __mdspan_detail::__is_representable_as<index_type>(__other.required_span_size()),
+          "layout_right::mapping from layout_stride ctor: other.required_span_size() must be representable as "
+          "index_type.");
+    }
+  }
 
   _LIBCPP_HIDE_FROM_ABI constexpr mapping& operator=(const mapping&) noexcept = default;
 
diff --git a/libcxx/include/__mdspan/layout_stride.h b/libcxx/include/__mdspan/layout_stride.h
new file mode 100644
index 000000000000000..77934bfa11d9de0
--- /dev/null
+++ b/libcxx/include/__mdspan/layout_stride.h
@@ -0,0 +1,366 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___MDSPAN_LAYOUT_STRIDE_H
+#define _LIBCPP___MDSPAN_LAYOUT_STRIDE_H
+
+#include <__assert>
+#include <__config>
+#include <__fwd/mdspan.h>
+#include <__mdspan/extents.h>
+#include <__type_traits/is_constructible.h>
+#include <__type_traits/is_convertible.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__utility/as_const.h>
+#include <__utility/integer_sequence.h>
+#include <__utility/swap.h>
+#include <array>
+#include <cinttypes>
+#include <cstddef>
+#include <limits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+
+namespace __mdspan_detail {
+template <class _Layout, class _Mapping>
+constexpr bool __is_mapping_of =
+    is_same_v<typename _Layout::template mapping<typename _Mapping::extents_type>, _Mapping>;
+
+template <class _Mapping>
+concept __layout_mapping_alike = requires {
+  requires __is_mapping_of<typename _Mapping::layout_type, _Mapping>;
+  requires __is_extents_v<typename _Mapping::extents_type>;
+  { _Mapping::is_always_strided() } -> same_as<bool>;
+  { _Mapping::is_always_exhaustive() } -> same_as<bool>;
+  { _Mapping::is_always_unique() } -> same_as<bool>;
+  bool_constant<_Mapping::is_always_strided()>::value;
+  bool_constant<_Mapping::is_always_exhaustive()>::value;
+  bool_constant<_Mapping::is_always_unique()>::value;
+};
+} // namespace __mdspan_detail
+
+template <class _Extents>
+class layout_stride::mapping {
+public:
+  static_assert(__mdspan_detail::__is_extents<_Extents>::value,
+                "layout_stride::mapping template argument must be a specialization of extents.");
+
+  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_stride;
+
+private:
+  static constexpr rank_type __rank_ = extents_type::rank();
+
+  // Used for default construction check and mandates
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool __required_span_size_is_representable(const extents_type& __ext) {
+    if constexpr (__rank_ == 0)
+      return true;
+
+    index_type __prod = __ext.extent(0);
+    for (rank_type __r = 1; __r < __rank_; __r++) {
+      bool __overflowed = __builtin_mul_overflow(__prod, __ext.extent(__r), &__prod);
+      if (__overflowed)
+        return false;
+    }
+    return true;
+  }
+
+  template <class _OtherIndexType>
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool
+  __required_span_size_is_representable(const extents_type& __ext, span<_OtherIndexType, __rank_> __strides) {
+    if constexpr (__rank_ == 0)
+      return true;
+
+    index_type __size = 1;
+    for (rank_type __r = 0; __r < __rank_; __r++) {
+      // We can only check correct conversion of _OtherIndexType if it is an integral
+      if constexpr (is_integral_v<_OtherIndexType>) {
+        using _CommonType = common_type_t<index_type, _OtherIndexType>;
+        if (static_cast<_CommonType>(__strides[__r]) > static_cast<_CommonType>(numeric_limits<index_type>::max()))
+          return false;
+      }
+      if (__ext.extent(__r) == static_cast<index_type>(0))
+        return true;
+      index_type __prod     = (__ext.extent(__r) - 1);
+      bool __overflowed_mul = __builtin_mul_overflow(__prod, static_cast<index_type>(__strides[__r]), &__prod);
+      if (__overflowed_mul)
+        return false;
+      bool __overflowed_add = __builtin_add_overflow(__size, __prod, &__size);
+      if (__overflowed_add)
+        return false;
+    }
+    return true;
+  }
+
+  // compute offset of a strided layout mapping
+  template <class _StridedMapping>
+  _LIBCPP_HIDE_FROM_ABI static constexpr index_type __offset(const _StridedMapping& __mapping) {
+    if constexpr (_StridedMapping::extents_type::rank() == 0) {
+      return static_cast<index_type>(__mapping());
+    } else if (__mapping.required_span_size() == static_cast<typename _StridedMapping::index_type>(0)) {
+      return static_cast<index_type>(0);
+    } else {
+      return [&]<size_t... _Pos>(index_sequence<_Pos...>) {
+        return static_cast<index_type>(__mapping((_Pos ? 0 : 0)...));
+      }(make_index_sequence<__rank_>());
+    }
+  }
+
+  // compute the permutation for sorting the stride array
+  // we never actually sort the stride array
+  _LIBCPP_HIDE_FROM_ABI constexpr void __bubble_sort_by_strides(array<rank_type, __rank_>& __permute) const {
+    for (rank_type __i = __rank_ - 1; __i > 0; __i--) {
+      for (rank_type __r = 0; __r < __i; __r++) {
+        if (__strides_[__permute[__r]] > __strides_[__permute[__r + 1]]) {
+          swap(__permute[__r], __permute[__r + 1]);
+        } else {
+          // if two strides are the same then one of the associated extents must be 1 or 0
+          // both could be, but you can't have one larger than 1 come first
+          if ((__strides_[__permute[__r]] == __strides_[__permute[__r + 1]]) &&
+              (__extents_.extent(__permute[__r]) > static_cast<index_type>(1)))
+            swap(__permute[__r], __permute[__r + 1]);
+        }
+      }
+    }
+  }
+
+  static_assert((extents_type::rank_dynamic() > 0) || __required_span_size_is_representable(extents_type()),
+                "layout_stride::mapping product of static extents must be representable as index_type.");
+
+public:
+  // [mdspan.layout.stride.cons], constructors
+  _LIBCPP_HIDE_FROM_ABI constexpr mapping() noexcept : __extents_(extents_type()) {
+    // Note the nominal precondition is covered by above static assert since
+    // if rank_dynamic is != 0 required_span_size is zero for default construction
+    if constexpr (__rank_ > 0) {
+      index_type __stride = 1;
+      for (rank_type __r = __rank_ - 1; __r > static_cast<rank_type>(0); __r--) {
+        __strides_[__r] = __stride;
+        __stride *= __extents_.extent(__r);
+      }
+      __strides_[0] = __stride;
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr mapping(const mapping&) noexcept = default;
+
+  template <class _OtherIndexType>
+    requires(is_convertible_v<const _OtherIndexType&, index_type> &&
+             is_nothrow_constructible_v<index_type, const _OtherIndexType&>)
+  _LIBCPP_HIDE_FROM_ABI constexpr mapping(const extents_type& __ext, span<_OtherIndexType, __rank_> __strides) noexcept
+      : __extents_(__ext), __strides_([&]<size_t... _Pos>(index_sequence<_Pos...>) {
+          return __mdspan_detail::__possibly_empty_array<index_type, __rank_>{
+              static_cast<index_type>(std::as_const(__strides[_Pos]))...};
+        }(make_index_sequence<__rank_>())) {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        ([&]<size_t... _Pos>(index_sequence<_Pos...>) {
+          // For integrals we can do a pre-conversion check, for other types not
+          if constexpr (is_integral_v<_OtherIndexType>) {
+            return ((__strides[_Pos] > static_cast<_OtherIndexType>(0)) && ... && true);
+          } else {
+            return ((static_cast<index_type>(__strides[_Pos]) > static_cast<index_type>(0)) && ... && true);
+          }
+        }(make_index_sequence<__rank_>())),
+        "layout_stride::mapping ctor: all strides must be greater than 0");
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        __required_span_size_is_representable(__ext, __strides),
+        "layout_stride::mapping ctor: required span size is not representable as index_type.");
+    if constexpr (__rank_ > 1) {
+      _LIBCPP_ASSERT_UNCATEGORIZED(
+          ([&]<size_t... _Pos>(index_sequence<_Pos...>) {
+            // basically sort the dimensions based on strides and extents, sorting is represented in permute array
+            array<rank_type, __rank_> __permute{_Pos...};
+            __bubble_sort_by_strides(__permute);
+
+            // check that this permutations represents a growing set
+            for (rank_type __i = 1; __i < __rank_; __i++)
+              if (static_cast<index_type>(__strides[__permute[__i]]) <
+                  static_cast<index_type>(__strides[__permute[__i - 1]]) * __extents_.extent(__permute[__i - 1]))
+                return false;
+            return true;
+          }(make_index_sequence<__rank_>())),
+          "layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
+    }
+  }
+
+  template <class _OtherIndexType>
+    requires(is_convertible_v<const _OtherIndexType&, index_type> &&
+             is_nothrow_constructible_v<index_type, const _OtherIndexType&>)
+  _LIBCPP_HIDE_FROM_ABI constexpr mapping(const extents_type& __ext,
+                                          const array<_OtherIndexType, __rank_>& __strides) noexcept
+      : mapping(__ext, span(__strides)) {}
+
+  template <class _StridedLayoutMapping>
+    requires(__mdspan_detail::__layout_mapping_alike<_StridedLayoutMapping> &&
+             is_constructible_v<extents_type, typename _StridedLayoutMapping::extents_type> &&
+             _StridedLayoutMapping::is_always_unique() && _StridedLayoutMapping::is_always_strided())
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit(
+      !(is_convertible_v<typename _StridedLayoutMapping::extents_type, extents_type> &&
+        (__mdspan_detail::__is_mapping_of<layout_left, _StridedLayoutMapping> ||
+         __mdspan_detail::__is_mapping_of<layout_right, _StridedLayoutMapping> ||
+         __mdspan_detail::__is_mapping_of<layout_stride, _StridedLayoutMapping>)))
+      mapping(const _StridedLayoutMapping& __other) noexcept
+      : __extents_(__other.extents()), __strides_([&]<size_t... _Pos>(index_sequence<_Pos...>) {
+          // stride() only compiles for rank > 0
+          if constexpr (__rank_ > 0) {
+            return __mdspan_detail::__possibly_empty_array<index_type, __rank_>{
+                static_cast<index_type>(__other.stride(_Pos))...};
+          } else {
+            return __mdspan_detail::__possibly_empty_array<index_type, 0>{};
+          }
+        }(make_index_sequence<__rank_>())) {
+    // stride() only compiles for rank > 0
+    if constexpr (__rank_ > 0) {
+      _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+          ([&]<size_t... _Pos>(index_sequence<_Pos...>) {
+            return ((static_cast<index_type>(__other.stride(_Pos)) > static_cast<index_type>(0)) && ... && true);
+          }(make_index_sequence<__rank_>())),
+          "layout_stride::mapping converting ctor: all strides must be greater than 0");
+    }
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        __mdspan_detail::__is_representable_as<index_type>(__other.required_span_size()),
+        "layout_stride::mapping converting ctor: other.required_span_size() must be representable as index_type.");
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(static_cast<index_type>(0) == __offset(__other),
+                                        "layout_stride::mapping converting ctor: base offset of mapping must be zero.");
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr mapping& operator=(const mapping&) noexcept = default;
+
+  // [mdspan.layout.stride.obs], observers
+  _LIBCPP_HIDE_FROM_ABI constexpr const extents_type& extents() const noexcept { return __extents_; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr array<index_type, __rank_> strides() const noexcept {
+    return [&]<size_t... _Pos>(index_sequence<_Pos...>) {
+      return array<index_type, __rank_>{__strides_[_Pos]...};
+    }(make_index_sequence<__rank_>());
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr index_type required_span_size() const noexcept {
+    if constexpr (__rank_ == 0) {
+      return static_cast<index_type>(1);
+    } else {
+      return [&]<size_t... _Pos>(index_sequence<_Pos...>) {
+        if ((__extents_.extent(_Pos) * ... * 1) == 0)
+          return static_cast<index_type>(0);
+        else
+          return static_cast<index_type>(
+              static_cast<index_type>(1) +
+              (((__extents_.extent(_Pos) - static_cast<index_type>(1)) * __strides_[_Pos]) + ... +
+               static_cast<index_type>(0)));
+      }(make_index_sequence<__rank_>());
+    }
+  }
+
+  template <class... _Indices>
+    requires((sizeof...(_Indices) == __rank_) && (is_convertible_v<_Indices, index_type> && ...) &&
+             (is_nothrow_constructible_v<index_type, _Indices> && ...))
+  _LIBCPP_HIDE_FROM_ABI constexpr index_type operator()(_Indices... __idx) const noexcept {
+    // Mappings are generally meant to be used for accessing allocations and are meant to guarantee to never
+    // return a value exceeding required_span_size(), which is used to know how large an allocation one needs
+    // Thus, this is a canonical point in multi-dimensional data structures to make invalid element access checks
+    // However, mdspan does check this on its own, so for now we avoid double checking in hardened mode
+    _LIBCPP_ASSERT_UNCATEGORIZED(__mdspan_detail::__is_multidimensional_index_in(__extents_, __idx...),
+                                 "layout_stride::mapping: out of bounds indexing");
+    return [&]<size_t... _Pos>(index_sequence<_Pos...>) {
+      return ((static_cast<index_type>(__idx) * __strides_[_Pos]) + ... + index_type(0));
+    }(make_index_sequence<sizeof...(_Indices)>());
+  }
+
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool is_always_unique() noexcept { return true; }
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool is_always_exhaustive() noexcept { return false; }
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool is_always_strided() noexcept { return true; }
+
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool is_unique() noexcept { return true; }
+  // The answer of this function is fairly complex in the case where one or more
+  // extents are zero.
+  // Technically it is meaningless to query is_exhaustive() in that case, but unfortunately
+  // the way the standard defines this function, we can't give a simple true or false then.
+  _LIBCPP_HIDE_FROM_ABI constexpr bool is_exhaustive() const noexcept {
+    if constexpr (__rank_ == 0)
+      return true;
+    else {
+      index_type __span_size = required_span_size();
+      if (__span_size == static_cast<index_type>(0)) {
+        if constexpr (__rank_ == 1)
+          return __strides_[0] == 1;
+        else {
+          rank_type __r_largest = 0;
+          for (rank_type __r = 1; __r < __rank_; __r++)
+            if (__strides_[__r] > __strides_[__r_largest])
+              __r_largest = __r;
+          for (rank_type __r = 0; __r < __rank_; __r++)
+            if (__extents_.extent(__r) == 0 && __r != __r_largest)
+              return false;
+          return true;
+        }
+      } else {
+        return required_span_size() == [&]<size_t... _Pos>(index_sequence<_Pos...>) {
+          return (__extents_.extent(_Pos) * ... * static_cast<index_type>(1));
+        }(make_index_sequence<__rank_>());
+      }
+    }
+  }
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool is_strided() noexcept { return true; }
+
+  // according to the standard layout_stride does not have a constraint on stride(r) for rank>0
+  // it still has the precondition though
+  _LIBCPP_HIDE_FROM_ABI constexpr index_type stride(rank_type __r) const noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__r < __rank_, "layout_stride::mapping::stride(): invalid rank index");
+    return __strides_[__r];
+  }
+
+  template <class _OtherMapping>
+    requires(__mdspan_detail::__layout_mapping_alike<_OtherMapping> &&
+             (_OtherMapping::extents_type::rank() == __rank_) && _OtherMapping::is_always_strided())
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const mapping& __lhs, const _OtherMapping& __rhs) noexcept {
+    if (__offset(__rhs))
+      return false;
+    if constexpr (__rank_ == 0)
+      return true;
+    else {
+      return __lhs.extents() == __rhs.extents() && [&]<size_t... _Pos>(index_sequence<_Pos...>) {
+        // avoid warning when comparing signed and unsigner integers and pick the wider of two types
+        using _CommonType = common_type_t<index_type, typename _OtherMapping::index_type>;
+        return ((static_cast<_CommonType>(__lhs.stride(_Pos)) == static_cast<_CommonType>(__rhs.stride(_Pos))) && ... &&
+                true);
+      }(make_index_sequence<__rank_>());
+    }
+  }
+
+private:
+  _LIBCPP_NO_UNIQUE_ADDRESS extents_type __extents_{};
+  _LIBCPP_NO_UNIQUE_ADDRESS __mdspan_detail::__possibly_empty_array<index_type, __rank_> __strides_{};
+};
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___MDSPAN_LAYOUT_STRIDE_H
diff --git a/libcxx/include/mdspan b/libcxx/include/mdspan
index e38816657751b47..05f612593f4b5ba 100644
--- a/libcxx/include/mdspan
+++ b/libcxx/include/mdspan
@@ -190,6 +190,63 @@ namespace std {
   };
 }
 
+// layout_stride synopsis
+
+namespace std {
+  template<class Extents>
+  class layout_stride::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;
+    using layout_type = layout_stride;
+
+  private:
+    static constexpr rank_type rank_ = extents_type::rank();    // exposition only
+
+  public:
+    // [mdspan.layout.stride.cons], constructors
+    constexpr mapping() noexcept;
+    constexpr mapping(const mapping&) noexcept = default;
+    template<class OtherIndexType>
+      constexpr mapping(const extents_type&, span<OtherIndexType, rank_>) noexcept;
+    template<class OtherIndexType>
+      constexpr mapping(const extents_type&, const array<OtherIndexType, rank_>&) noexcept;
+
+    template<class StridedLayoutMapping>
+      constexpr explicit(see below) mapping(const StridedLayoutMapping&) noexcept;
+
+    constexpr mapping& operator=(const mapping&) noexcept = default;
+
+    // [mdspan.layout.stride.obs], observers
+    constexpr const extents_type& extents() const noexcept { return extents_; }
+    constexpr array<index_type, rank_> strides() const noexcept { return strides_; }
+
+    constexpr index_type required_span_size() const noexcept;
+
+    template<class... Indices>
+      constexpr index_type operator()(Indices...) const noexcept;
+
+    static constexpr bool is_always_unique() noexcept { return true; }
+    static constexpr bool is_always_exhaustive() noexcept { return false; }
+    static constexpr bool is_always_strided() noexcept { return true; }
+
+    static constexpr bool is_unique() noexcept { return true; }
+    constexpr bool is_exhaustive() const noexcept;
+    static constexpr bool is_strided() noexcept { return true; }
+
+    constexpr index_type stride(rank_type i) const noexcept { return strides_[i]; }
+
+    template<class OtherMapping>
+      friend constexpr bool operator==(const mapping&, const OtherMapping&) noexcept;
+
+  private:
+    extents_type extents_{};                    // exposition only
+    array<index_type, rank_> strides_{};        // exposition only
+  };
+}
+
 // default_accessor synopsis
 
 namespace std {
@@ -348,7 +405,9 @@ namespace std {
 #include <__mdspan/extents.h>
 #include <__mdspan/layout_left.h>
 #include <__mdspan/layout_right.h>
+#include <__mdspan/layout_stride.h>
 #include <__mdspan/mdspan.h>
+#include <version>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 3e5a8a391b6e69c..41158212c9a86cf 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1510,6 +1510,7 @@ module std_private_mdspan_extents          [system] {
 }
 module std_private_mdspan_layout_left      [system] { header "__mdspan/layout_left.h" }
 module std_private_mdspan_layout_right     [system] { header "__mdspan/layout_right.h" }
+module std_private_mdspan_layout_stride    [system] { header "__mdspan/layout_stride.h" }
 module std_private_mdspan_mdspan           [system] { header "__mdspan/mdspan.h" }
 module std_private_mdspan_mdspan_fwd       [system] { header "__fwd/mdspan.h" }
 
diff --git a/libcxx/include/version b/libcxx/include/version
index 4fdfec4f34e3f6e..c41ce745b59304b 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -427,7 +427,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # define __cpp_lib_forward_like                         202207L
 # define __cpp_lib_invoke_r                             202106L
 # define __cpp_lib_is_scoped_enum                       202011L
-// # define __cpp_lib_mdspan                               202207L
+# define __cpp_lib_mdspan                               202207L
 // # define __cpp_lib_move_only_function                   202110L
 # undef  __cpp_lib_optional
 # define __cpp_lib_optional                             202110L
diff --git a/libcxx/modules/std/mdspan.inc b/libcxx/modules/std/mdspan.inc
index 888c94ad59cfd9a..ba3f3926c3abdd1 100644
--- a/libcxx/modules/std/mdspan.inc
+++ b/libcxx/modules/std/mdspan.inc
@@ -18,7 +18,7 @@ export namespace std {
   // [mdspan.layout], layout mapping
   using std::layout_left;
   using std::layout_right;
-  // using std::layout_stride;
+  using std::layout_stride;
 
   // [mdspan.accessor.default], class template default_accessor
   using std::default_accessor;
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_left/assert.ctor.layout_stride.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_left/assert.ctor.layout_stride.pass.cpp
new file mode 100644
index 000000000000000..25ffa46a90ef27a
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_left/assert.ctor.layout_stride.pass.cpp
@@ -0,0 +1,83 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-hardening-mode=unchecked
+// XFAIL: availability-verbose_abort-missing
+// ADDITIONAL_COMPILE_FLAGS: -Wno-ctad-maybe-unsupported
+
+// FIXME: https://github.com/llvm/llvm-project/issues/64719
+// There appear to be some issues around ctad which make it
+// currently impossible to get this code warning free.
+// Thus added the additional compile flag above
+
+// <mdspan>
+
+// template<class OtherExtents>
+//   constexpr explicit(extents_type::rank() > 0)
+//     mapping(const layout_stride::mapping<OtherExtents>& other);
+//
+// Constraints: is_constructible_v<extents_type, OtherExtents> is true.
+//
+// Preconditions:
+//   - If extents_type::rank() > 0 is true, then for all r in the range [0, extents_type::rank()),
+//     other.stride(r) equals other.extents().fwd-prod-of-extents(r), and
+//   - other.required_span_size() is representable as a value of type index_type ([basic.fundamental]).
+//
+// Effects: Direct-non-list-initializes extents_ with other.extents().
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+
+  // working case
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(5), std::array<int, 1>{1});
+    [[maybe_unused]] std::layout_left::mapping<std::extents<size_t, 5>> m(arg); // should work
+  }
+  // mismatch of static extent
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(5), std::array<int, 1>{1});
+    TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_left::mapping<std::extents<int, 3>> m(arg); }()),
+                               "extents construction: mismatch of provided arguments with static extents.");
+  }
+  // non-representability of extents itself
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(500), std::array<int, 1>{1});
+    TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_left::mapping<std::extents<char, D>> m(arg); }()),
+                               "extents ctor: arguments must be representable as index_type and nonnegative");
+  }
+  // non-representability of required span size
+  {
+    std::layout_stride::mapping arg(std::extents<int, D, D>(100, 3), std::array<int, 2>{1, 100});
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_left::mapping<std::extents<char, D, D>> m(arg); }()),
+        "layout_left::mapping from layout_stride ctor: other.required_span_size() must be "
+        "representable as index_type.");
+  }
+  // strides are not layout_left compatible
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(5), std::array<int, 1>{2});
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_left::mapping<std::extents<size_t, 5>> m(arg); }()),
+        "layout_left::mapping from layout_stride ctor: strides are not compatible with layout_left.");
+  }
+  {
+    std::layout_stride::mapping arg(std::extents<int, D, D>(100, 3), std::array<int, 2>{2, 200});
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_left::mapping<std::extents<int, D, D>> m(arg); }()),
+        "layout_left::mapping from layout_stride ctor: strides are not compatible with layout_left.");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_right/assert.ctor.layout_stride.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_right/assert.ctor.layout_stride.pass.cpp
new file mode 100644
index 000000000000000..b8b78fceebbd5de
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_right/assert.ctor.layout_stride.pass.cpp
@@ -0,0 +1,83 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-hardening-mode=unchecked
+// XFAIL: availability-verbose_abort-missing
+// ADDITIONAL_COMPILE_FLAGS: -Wno-ctad-maybe-unsupported
+
+// FIXME: https://github.com/llvm/llvm-project/issues/64719
+// There appear to be some issues around ctad which make it
+// currently impossible to get this code warning free.
+// Thus added the additional compile flag above
+
+// <mdspan>
+
+// template<class OtherExtents>
+//   constexpr explicit(extents_type::rank() > 0)
+//     mapping(const layout_stride::mapping<OtherExtents>& other);
+//
+// Constraints: is_constructible_v<extents_type, OtherExtents> is true.
+//
+// Preconditions:
+//   - If extents_type::rank() > 0 is true, then for all r in the range [0, extents_type::rank()),
+//     other.stride(r) equals other.extents().fwd-prod-of-extents(r), and
+//   - other.required_span_size() is representable as a value of type index_type ([basic.fundamental]).
+//
+// Effects: Direct-non-list-initializes extents_ with other.extents().
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+
+  // working case
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(5), std::array<int, 1>{1});
+    [[maybe_unused]] std::layout_right::mapping<std::extents<size_t, 5>> m(arg); // should work
+  }
+  // mismatch of static extent
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(5), std::array<int, 1>{1});
+    TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_right::mapping<std::extents<int, 3>> m(arg); }()),
+                               "extents construction: mismatch of provided arguments with static extents.");
+  }
+  // non-representability of extents itself
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(500), std::array<int, 1>{1});
+    TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_right::mapping<std::extents<char, D>> m(arg); }()),
+                               "extents ctor: arguments must be representable as index_type and nonnegative");
+  }
+  // non-representability of required span size
+  {
+    std::layout_stride::mapping arg(std::extents<int, D, D>(100, 3), std::array<int, 2>{3, 1});
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_right::mapping<std::extents<char, D, D>> m(arg); }()),
+        "layout_right::mapping from layout_stride ctor: other.required_span_size() must be "
+        "representable as index_type.");
+  }
+  // strides are not layout_right compatible
+  {
+    std::layout_stride::mapping arg(std::extents<int, D>(5), std::array<int, 1>{2});
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_right::mapping<std::extents<size_t, 5>> m(arg); }()),
+        "layout_right::mapping from layout_stride ctor: strides are not compatible with layout_right.");
+  }
+  {
+    std::layout_stride::mapping arg(std::extents<int, D, D>(100, 3), std::array<int, 2>{6, 2});
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_right::mapping<std::extents<int, D, D>> m(arg); }()),
+        "layout_right::mapping from layout_stride ctor: strides are not compatible with layout_right.");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.conversion.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.conversion.pass.cpp
new file mode 100644
index 000000000000000..e8bad8b0d512495
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.conversion.pass.cpp
@@ -0,0 +1,112 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-hardening-mode=unchecked
+// XFAIL: availability-verbose_abort-missing
+
+// <mdspan>
+
+// template<class StridedLayoutMapping>
+//   constexpr explicit(see below)
+//     mapping(const StridedLayoutMapping& other) noexcept;
+//
+// Constraints:
+//   - layout-mapping-alike<StridedLayoutMapping> is satisfied.
+//   - is_constructible_v<extents_type, typename StridedLayoutMapping::extents_type> is true.
+//   - StridedLayoutMapping::is_always_unique() is true.
+//   - StridedLayoutMapping::is_always_strided() is true.
+//
+// Preconditions:
+//   - StridedLayoutMapping meets the layout mapping requirements ([mdspan.layout.policy.reqmts]),
+//   - other.stride(r) > 0 is true for every rank index r of extents(),
+//   - other.required_span_size() is representable as a value of type index_type ([basic.fundamental]), and
+//   - OFFSET(other) == 0 is true.
+//
+// Effects: Direct-non-list-initializes extents_ with other.extents(), and for all d in the range [0, rank_),
+//          direct-non-list-initializes strides_[d] with other.stride(d).
+//
+// Remarks: The expression inside explicit is equivalent to:
+//   - !(is_convertible_v<typename StridedLayoutMapping::extents_type, extents_type> &&
+//       (is-mapping-of<layout_left, LayoutStrideMapping> ||
+//        is-mapping-of<layout_right, LayoutStrideMapping> ||
+//        is-mapping-of<layout_stride, LayoutStrideMapping>))
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+#include "../../../../../std/containers/views/mdspan/CustomTestLayouts.h"
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+
+  // working case
+  {
+    std::extents<int, D, D> arg_exts{100, 5};
+    std::layout_stride::mapping<std::extents<int, D, D>> arg(arg_exts, std::array<int, 2>{1, 100});
+    [[maybe_unused]] std::layout_stride::mapping<std::extents<size_t, D, 5>> m(arg); // should work
+  }
+  // mismatch of static extent
+  {
+    std::extents<int, D, D> arg_exts{100, 5};
+    std::layout_stride::mapping<std::extents<int, D, D>> arg(arg_exts, std::array<int, 2>{1, 100});
+    TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_stride::mapping<std::extents<int, D, 3>> m(arg); }()),
+                               "extents construction: mismatch of provided arguments with static extents.");
+  }
+  // non-representability of extents itself
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::layout_stride::mapping<std::extents<char, D>> m(
+              std::layout_stride::mapping<std::extents<int, D>>(std::extents<int, D>(500), std::array<int, 1>{1}));
+        }()),
+        "extents ctor: arguments must be representable as index_type and nonnegative");
+  }
+  // all strides must be larger than zero
+  {
+    always_convertible_layout::mapping<std::dextents<int, 2>> offset_map(std::dextents<int, 2>{10, 10}, 100, -1);
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_stride::template mapping<std::extents<char, D, D>> m(offset_map); }()),
+        "layout_stride::mapping converting ctor: all strides must be greater than 0");
+  }
+  // required_span_size not representable, while individual extents are
+  {
+    std::extents<int, D, D> arg_exts{100, 5};
+    std::layout_stride::mapping<std::extents<int, D, D>> arg(arg_exts, std::array<int, 2>{1, 100});
+    // check extents would be constructible
+    [[maybe_unused]] std::extents<char, D, 5> e(arg_exts);
+    // but the product is not, so we can't use it for layout_stride
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_stride::template mapping<std::extents<char, D, 5>> m(arg); }()),
+        "layout_stride::mapping converting ctor: other.required_span_size() must be representable as index_type.");
+  }
+  // required_span_size not representable, while individual extents are, edge case
+  {
+    // required span size = (3-1)*50 + (10-1) * 3 + 1 = 128
+    std::extents<int, D, D> arg_exts{3, 10};
+    std::layout_stride::mapping<std::extents<int, D, D>> arg(arg_exts, std::array<int, 2>{50, 3});
+    // sanity check:
+    assert(arg.required_span_size() == 128);
+    // check extents would be constructible
+    [[maybe_unused]] std::extents<signed char, D, 10> e(arg_exts);
+    // but the product is not, so we can't use it for layout_stride
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_stride::template mapping<std::extents<signed char, D, 10>> m(arg); }()),
+        "layout_stride::mapping converting ctor: other.required_span_size() must be representable as index_type.");
+  }
+  // base offset must be 0 (i.e. mapping(0,...,0)==0) for a strided layout with positiv strides
+  {
+    always_convertible_layout::mapping<std::dextents<int, 2>> offset_map(std::dextents<int, 2>{10, 10}, 3);
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] { std::layout_stride::template mapping<std::extents<char, D, D>> m(offset_map); }()),
+        "layout_stride::mapping converting ctor: base offset of mapping must be zero.");
+  }
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.non_unique.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.non_unique.pass.cpp
new file mode 100644
index 000000000000000..ba1c81d35d8df49
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.non_unique.pass.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <mdspan>
+
+// template<class OtherIndexType>
+//   constexpr mapping(const extents_type& e, const array<OtherIndexType, rank_>& s) noexcept;
+//
+// Constraints:
+//   - is_convertible_v<const OtherIndexType&, index_type> is true, and
+//   - is_nothrow_constructible_v<index_type, const OtherIndexType&> is true.
+//
+// Preconditions:
+//   - s[i] > 0 is true for all i in the range [0, rank_).
+//   - REQUIRED-SPAN-SIZE(e, s) is representable as a value of type index_type ([basic.fundamental]).
+//   - If rank_ is greater than 0, then there exists a permutation P of the integers in the range [0, rank_),
+//     such that s[pi] >= s[pi_1] * e.extent(pi_1) is true for all i in the range [1, rank_), where
+//     pi is the ith element of P.
+//     [Note 1: For layout_stride, this condition is necessary and sufficient for is_unique() to be true. end note]
+//
+// Effects: Direct-non-list-initializes extents_ with e, and for all d in the range [0, rank_), direct-non-list-initializes strides_[d] with as_const(s[d]).
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+
+  // overlapping strides
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::layout_stride::template mapping<std::extents<unsigned, D, 5, 7>> m(
+              std::extents<unsigned, D, 5, 7>(20), std::array<unsigned, 3>{4, 1, 200});
+        }()),
+        "layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
+  }
+  // equal strides
+  {
+    // should work because one of the equal strides is associated with an extent of 1
+    [[maybe_unused]] std::layout_stride::template mapping<std::extents<unsigned, D, 5, 1>> m1(
+        std::extents<unsigned, D, 5, 1>(2), std::array<unsigned, 3>{5, 1, 5});
+    [[maybe_unused]] std::layout_stride::template mapping<std::extents<unsigned, D, 5, 2>> m2(
+        std::extents<unsigned, D, 5, 2>(1), std::array<unsigned, 3>{5, 1, 5});
+
+    // will fail because neither of the equal strides is associated with an extent of 1
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::layout_stride::template mapping<std::extents<unsigned, D, 5, 2>> m3(
+              std::extents<unsigned, D, 5, 2>(2), std::array<unsigned, 3>{5, 1, 5});
+        }()),
+        "layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
+  }
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.pass.cpp
new file mode 100644
index 000000000000000..6eea40c239f09d4
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_array.pass.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-hardening-mode=unchecked
+// XFAIL: availability-verbose_abort-missing
+
+// <mdspan>
+
+// template<class OtherIndexType>
+//   constexpr mapping(const extents_type& e, const array<OtherIndexType, rank_>& s) noexcept;
+//
+// Constraints:
+//   - is_convertible_v<const OtherIndexType&, index_type> is true, and
+//   - is_nothrow_constructible_v<index_type, const OtherIndexType&> is true.
+//
+// Preconditions:
+//   - s[i] > 0 is true for all i in the range [0, rank_).
+//   - REQUIRED-SPAN-SIZE(e, s) is representable as a value of type index_type ([basic.fundamental]).
+//   - If rank_ is greater than 0, then there exists a permutation P of the integers in the range [0, rank_),
+//     such that s[pi] >= s[pi_1] * e.extent(pi_1) is true for all i in the range [1, rank_), where
+//     pi is the ith element of P.
+//     [Note 1: For layout_stride, this condition is necessary and sufficient for is_unique() to be true. end note]
+//
+// Effects: Direct-non-list-initializes extents_ with e, and for all d in the range [0, rank_), direct-non-list-initializes strides_[d] with as_const(s[d]).
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+
+  // the extents are representable but the product with strides is not, so we can't use it for layout_stride
+  TEST_LIBCPP_ASSERT_FAILURE(
+      ([=] {
+        std::layout_stride::template mapping<std::extents<char, D, 5>> m(
+            std::extents<char, D, 5>(20), std::array<int, 2>{20, 1});
+      }()),
+      "layout_stride::mapping ctor: required span size is not representable as index_type.");
+
+  // check that if we first overflow in strides conversion we also fail
+  static_assert(static_cast<unsigned char>(257u) == 1);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      ([=] {
+        std::layout_stride::template mapping<std::extents<unsigned char, D, 5>> m(
+            std::extents<unsigned char, D, 5>(20), std::array<unsigned, 2>{257, 1});
+      }()),
+      "layout_stride::mapping ctor: required span size is not representable as index_type.");
+
+  // negative strides are not allowed, check with unsigned index_type so we make sure we catch that
+  TEST_LIBCPP_ASSERT_FAILURE(
+      ([=] {
+        std::layout_stride::template mapping<std::extents<unsigned, D, 5>> m(
+            std::extents<unsigned, D, 5>(20), std::array<int, 2>{20, -1});
+      }()),
+      "layout_stride::mapping ctor: all strides must be greater than 0");
+  // zero strides are not allowed, check with unsigned index_type so we make sure we catch that
+  TEST_LIBCPP_ASSERT_FAILURE(
+      ([=] {
+        std::layout_stride::template mapping<std::extents<unsigned, D, 5>> m(
+            std::extents<unsigned, D, 5>(20), std::array<unsigned, 2>{20, 0});
+      }()),
+      "layout_stride::mapping ctor: all strides must be greater than 0");
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.non_unique.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.non_unique.pass.cpp
new file mode 100644
index 000000000000000..b20f65762407477
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.non_unique.pass.cpp
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <mdspan>
+
+// template<class OtherIndexType>
+//   constexpr mapping(const extents_type& e, span<OtherIndexType, rank_> s) noexcept;
+//
+// Constraints:
+//   - is_convertible_v<const OtherIndexType&, index_type> is true, and
+//   - is_nothrow_constructible_v<index_type, const OtherIndexType&> is true.
+//
+// Preconditions:
+//   - s[i] > 0 is true for all i in the range [0, rank_).
+//   - REQUIRED-SPAN-SIZE(e, s) is representable as a value of type index_type ([basic.fundamental]).
+//   - If rank_ is greater than 0, then there exists a permutation P of the integers in the range [0, rank_),
+//     such that s[pi] >= s[pi_1] * e.extent(pi_1) is true for all i in the range [1, rank_), where
+//     pi is the ith element of P.
+//     [Note 1: For layout_stride, this condition is necessary and sufficient for is_unique() to be true. end note]
+//
+// Effects: Direct-non-list-initializes extents_ with e, and for all d in the range [0, rank_), direct-non-list-initializes strides_[d] with as_const(s[d]).
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+
+  // overlapping strides
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::array<unsigned, 3> strides{4, 1, 200};
+          std::layout_stride::template mapping<std::extents<unsigned, D, 5, 7>> m(
+              std::extents<unsigned, D, 5, 7>(20), std::span(strides));
+        }()),
+        "layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
+  }
+
+  // equal strides
+  {
+    // should work because one of the equal strides is associated with an extent of 1
+    std::array<unsigned, 3> strides{5, 1, 5};
+    [[maybe_unused]] std::layout_stride::template mapping<std::extents<unsigned, D, 5, 1>> m1(
+        std::extents<unsigned, D, 5, 1>(2), std::span(strides));
+    [[maybe_unused]] std::layout_stride::template mapping<std::extents<unsigned, D, 5, 2>> m2(
+        std::extents<unsigned, D, 5, 2>(1), std::span(strides));
+
+    // will fail because neither of the equal strides is associated with an extent of 1
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::layout_stride::template mapping<std::extents<unsigned, D, 5, 2>> m3(
+              std::extents<unsigned, D, 5, 2>(2), std::span(strides));
+        }()),
+        "layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
+  }
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.pass.cpp
new file mode 100644
index 000000000000000..cd06bbb545dd52d
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.ctor.extents_span.pass.cpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-hardening-mode=unchecked
+// XFAIL: availability-verbose_abort-missing
+
+// <mdspan>
+
+// template<class OtherIndexType>
+//   constexpr mapping(const extents_type& e, span<OtherIndexType, rank_> s) noexcept;
+//
+// Constraints:
+//   - is_convertible_v<const OtherIndexType&, index_type> is true, and
+//   - is_nothrow_constructible_v<index_type, const OtherIndexType&> is true.
+//
+// Preconditions:
+//   - s[i] > 0 is true for all i in the range [0, rank_).
+//   - REQUIRED-SPAN-SIZE(e, s) is representable as a value of type index_type ([basic.fundamental]).
+//   - If rank_ is greater than 0, then there exists a permutation P of the integers in the range [0, rank_),
+//     such that s[pi] >= s[pi_1] * e.extent(pi_1) is true for all i in the range [1, rank_), where
+//     pi is the ith element of P.
+//     [Note 1: For layout_stride, this condition is necessary and sufficient for is_unique() to be true. end note]
+//
+// Effects: Direct-non-list-initializes extents_ with e, and for all d in the range [0, rank_), direct-non-list-initializes strides_[d] with as_const(s[d]).
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+
+  // value out of range
+  {
+    // the extents are representable but the product with strides is not, so we can't use it for layout_stride
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::array<int, 2> strides{20, 1};
+          std::layout_stride::template mapping<std::extents<char, D, 5>> m(
+              std::extents<char, D, 5>(20), std::span(strides));
+        }()),
+        "layout_stride::mapping ctor: required span size is not representable as index_type.");
+
+    // check that if we first overflow in strides conversion we also fail
+    static_assert(static_cast<unsigned char>(257u) == 1);
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::array<unsigned, 2> strides{257, 1};
+          std::layout_stride::template mapping<std::extents<unsigned char, D, 5>> m(
+              std::extents<unsigned char, D, 5>(20), std::span(strides));
+        }()),
+        "layout_stride::mapping ctor: required span size is not representable as index_type.");
+
+    // negative strides are not allowed, check with unsigned index_type so we make sure we catch that
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::array<int, 2> strides{20, -1};
+          std::layout_stride::template mapping<std::extents<unsigned, D, 5>> m(
+              std::extents<unsigned, D, 5>(20), std::span(strides));
+        }()),
+        "layout_stride::mapping ctor: all strides must be greater than 0");
+    // zero strides are not allowed, check with unsigned index_type so we make sure we catch that
+    TEST_LIBCPP_ASSERT_FAILURE(
+        ([=] {
+          std::array<unsigned, 2> strides{20, 0};
+          std::layout_stride::template mapping<std::extents<unsigned, D, 5>> m(
+              std::extents<unsigned, D, 5>(20), std::span(strides));
+        }()),
+        "layout_stride::mapping ctor: all strides must be greater than 0");
+  }
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.index_operator.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.index_operator.pass.cpp
new file mode 100644
index 000000000000000..72e44bd97984c51
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.index_operator.pass.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <mdspan>
+
+// template<class... Indices>
+//   constexpr index_type operator()(Indices... i) const noexcept;
+//
+// Constraints:
+//   - sizeof...(Indices) == extents_type::rank() is true,
+//   - (is_convertible_v<Indices, index_type> && ...) is true, and
+//   - (is_nothrow_constructible_v<index_type, Indices> && ...) is true.
+//
+// Preconditions: extents_type::index-cast(i) is a multidimensional index in extents_ ([mdspan.overview]).
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  // value out of range
+  {
+    std::layout_stride::template mapping<std::extents<unsigned char, 5>> m;
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(-130), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(1000), "layout_stride::mapping: out of bounds indexing");
+  }
+  {
+    std::layout_stride::template mapping<std::extents<signed char, 5>> m;
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(-130), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(1000), "layout_stride::mapping: out of bounds indexing");
+  }
+  {
+    std::layout_stride::template mapping<std::dextents<unsigned char, 1>> m(
+        std::dextents<unsigned char, 1>(5), std::array<int, 1>{1});
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(-130), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(1000), "layout_stride::mapping: out of bounds indexing");
+  }
+  {
+    std::layout_stride::template mapping<std::dextents<signed char, 1>> m(
+        std::dextents<signed char, 1>(5), std::array<int, 1>{1});
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(-130), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(1000), "layout_stride::mapping: out of bounds indexing");
+  }
+  {
+    std::layout_stride::template mapping<std::dextents<int, 3>> m(
+        std::dextents<int, 3>(5, 7, 9), std::array<int, 3>{1, 10, 100});
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1, -1, -1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1, 0, 0), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(0, -1, 0), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(0, 0, -1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5, 3, 3), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(3, 7, 3), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(3, 3, 9), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5, 7, 9), "layout_stride::mapping: out of bounds indexing");
+  }
+  {
+    std::layout_stride::template mapping<std::dextents<unsigned, 3>> m(
+        std::dextents<int, 3>(5, 7, 9), std::array<int, 3>{1, 10, 100});
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1, -1, -1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(-1, 0, 0), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(0, -1, 0), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(0, 0, -1), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5, 3, 3), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(3, 7, 3), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(3, 3, 9), "layout_stride::mapping: out of bounds indexing");
+    TEST_LIBCPP_ASSERT_FAILURE(m(5, 7, 9), "layout_stride::mapping: out of bounds indexing");
+  }
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.stride.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.stride.pass.cpp
new file mode 100644
index 000000000000000..8a42861a0be0799
--- /dev/null
+++ b/libcxx/test/libcxx/containers/views/mdspan/layout_stride/assert.stride.pass.cpp
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-hardening-mode=unchecked
+// XFAIL: availability-verbose_abort-missing
+
+// <mdspan>
+
+// constexpr index_type stride(rank_type i) const noexcept { return strides_[i]; }
+
+// We intercept this inside layout_stride to give a consistent error message with
+// layout_left and layout_right, technically the precondition is coming from the
+// array access.
+
+#include <mdspan>
+#include <cassert>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  // value out of range
+  {
+    std::layout_stride::template mapping<std::dextents<int, 3>> m(
+        std::dextents<int, 3>(100, 100, 100), std::array<int, 3>{1, 100, 10000});
+
+    TEST_LIBCPP_ASSERT_FAILURE(m.stride(4), "invalid rank index");
+  }
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.conversion.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.conversion.pass.cpp
index cb0b2ed15fc1b48..8caaf2295c0a1b3 100644
--- a/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.conversion.pass.cpp
+++ b/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.conversion.pass.cpp
@@ -42,7 +42,7 @@
 #include <mdspan>
 
 #include "check_assertion.h"
-#include "../../../../../std/containers/views/mdspan/mdspan/CustomTestLayouts.h"
+#include "../../../../../std/containers/views/mdspan/CustomTestLayouts.h"
 
 // We use a funky mapping in this test that doesn't check the dynamic/static extents mismatch itself
 int main(int, char**) {
diff --git a/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.size.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.size.pass.cpp
index a10d89bc86d4b69..9b5b872bb3596f2 100644
--- a/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.size.pass.cpp
+++ b/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.size.pass.cpp
@@ -22,7 +22,7 @@
 #include <mdspan>
 
 #include "check_assertion.h"
-#include "../../../../../std/containers/views/mdspan/mdspan/CustomTestLayouts.h"
+#include "../../../../../std/containers/views/mdspan/CustomTestLayouts.h"
 
 // We use a funky mapping in this test where required_span_size is much smaller than the size of the index space
 int main(int, char**) {
diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index f6aeb837a7292f6..153335c3519c473 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -526,6 +526,7 @@ mdspan concepts
 mdspan cstddef
 mdspan limits
 mdspan span
+mdspan version
 memory atomic
 memory compare
 memory concepts
diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index 08fd94393f47320..f3f8956df64ee78 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -530,6 +530,7 @@ mdspan concepts
 mdspan cstddef
 mdspan limits
 mdspan span
+mdspan version
 memory atomic
 memory compare
 memory concepts
diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 33384a628e65c01..fb90523d5552ac3 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -532,6 +532,7 @@ mdspan concepts
 mdspan cstddef
 mdspan limits
 mdspan span
+mdspan version
 memory atomic
 memory compare
 memory concepts
diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 33384a628e65c01..fb90523d5552ac3 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -532,6 +532,7 @@ mdspan concepts
 mdspan cstddef
 mdspan limits
 mdspan span
+mdspan version
 memory atomic
 memory compare
 memory concepts
diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 8f6e8dd646df583..63ea6c09b1f5083 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -537,6 +537,7 @@ mdspan concepts
 mdspan cstddef
 mdspan limits
 mdspan span
+mdspan version
 memory atomic
 memory compare
 memory concepts
diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index d0d858056b1b00d..236dedd186c9268 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -386,6 +386,7 @@ mdspan concepts
 mdspan cstddef
 mdspan limits
 mdspan span
+mdspan version
 memory compare
 memory cstddef
 memory cstdint
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index d0d858056b1b00d..236dedd186c9268 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -386,6 +386,7 @@ mdspan concepts
 mdspan cstddef
 mdspan limits
 mdspan span
+mdspan version
 memory compare
 memory cstddef
 memory cstdint
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/CustomTestLayouts.h b/libcxx/test/std/containers/views/mdspan/CustomTestLayouts.h
similarity index 87%
rename from libcxx/test/std/containers/views/mdspan/mdspan/CustomTestLayouts.h
rename to libcxx/test/std/containers/views/mdspan/CustomTestLayouts.h
index e1d6e80afbd84ca..302eb2f5edd0193 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/CustomTestLayouts.h
+++ b/libcxx/test/std/containers/views/mdspan/CustomTestLayouts.h
@@ -14,8 +14,8 @@
 //
 //===---------------------------------------------------------------------===//
 
-#ifndef TEST_STD_CONTAINERS_VIEWS_MDSPAN_MDSPAN_CUSTOM_TEST_LAYOUTS_H
-#define TEST_STD_CONTAINERS_VIEWS_MDSPAN_MDSPAN_CUSTOM_TEST_LAYOUTS_H
+#ifndef TEST_STD_CONTAINERS_VIEWS_MDSPAN_CUSTOM_TEST_LAYOUTS_H
+#define TEST_STD_CONTAINERS_VIEWS_MDSPAN_CUSTOM_TEST_LAYOUTS_H
 
 #include <algorithm>
 #include <array>
@@ -205,9 +205,9 @@ constexpr auto construct_mapping(layout_wrapping_integral<Wraps>, Extents exts)
   return typename layout_wrapping_integral<Wraps>::template mapping<Extents>(exts, not_extents_constructible_tag{});
 }
 
-
 // This layout does not check convertibility of extents for its conversion ctor
 // Allows triggering mdspan's ctor static assertion on convertibility of extents
+// It also allows for negative strides and offsets via runtime arguments
 class always_convertible_layout {
 public:
   template <class Extents>
@@ -239,8 +239,10 @@ class always_convertible_layout::mapping {
 
 public:
   constexpr mapping() noexcept = delete;
-  constexpr mapping(const mapping& other) noexcept : extents_(other.extents()){};
-  constexpr mapping(const extents_type& ext) noexcept : extents_(ext){};
+  constexpr mapping(const mapping& other) noexcept
+      : extents_(other.extents_), offset_(other.offset_), scaling_(other.scaling_){};
+  constexpr mapping(const extents_type& ext, index_type offset = 0, index_type scaling = 1) noexcept
+      : extents_(ext), offset_(offset), scaling_(scaling){};
 
   template <class OtherExtents>
   constexpr mapping(const mapping<OtherExtents>& other) noexcept {
@@ -256,10 +258,14 @@ class always_convertible_layout::mapping {
     } else {
       extents_ = extents_type();
     }
+    offset_  = other.offset_;
+    scaling_ = other.scaling_;
   }
 
   constexpr mapping& operator=(const mapping& other) noexcept {
     extents_ = other.extents_;
+    offset_  = other.offset_;
+    scaling_ = other.scaling_;
     return *this;
   };
 
@@ -269,7 +275,7 @@ class always_convertible_layout::mapping {
     index_type size = 1;
     for (size_t r = 0; r < extents_type::rank(); r++)
       size *= extents_.extent(r);
-    return size;
+    return std::max(size * scaling_ + offset_, offset_);
   }
 
   template <std::integral... Indices>
@@ -277,16 +283,18 @@ class always_convertible_layout::mapping {
              (std::is_nothrow_constructible_v<index_type, Indices> && ...))
   constexpr index_type operator()(Indices... idx) const noexcept {
     std::array<index_type, extents_type::rank()> idx_a{static_cast<index_type>(static_cast<index_type>(idx))...};
-    return [&]<size_t... Pos>(std::index_sequence<Pos...>) {
-      index_type res = 0;
-      ((res = idx_a[extents_type::rank() - 1 - Pos] + extents_.extent(extents_type::rank() - 1 - Pos) * res), ...);
-      return res;
-    }(std::make_index_sequence<sizeof...(Indices)>());
+    return offset_ +
+           scaling_ * ([&]<size_t... Pos>(std::index_sequence<Pos...>) {
+             index_type res = 0;
+             ((res = idx_a[extents_type::rank() - 1 - Pos] + extents_.extent(extents_type::rank() - 1 - Pos) * res),
+              ...);
+             return res;
+           }(std::make_index_sequence<sizeof...(Indices)>()));
   }
 
-  static constexpr bool is_always_unique() noexcept { return false; }
+  static constexpr bool is_always_unique() noexcept { return true; }
   static constexpr bool is_always_exhaustive() noexcept { return true; }
-  static constexpr bool is_always_strided() noexcept { return false; }
+  static constexpr bool is_always_strided() noexcept { return true; }
 
   static constexpr bool is_unique() noexcept { return true; }
   static constexpr bool is_exhaustive() noexcept { return true; }
@@ -296,15 +304,15 @@ class always_convertible_layout::mapping {
     requires(extents_type::rank() > 0)
   {
     index_type s = 1;
-    for (rank_type i = extents_type::rank() - 1; i > r; i--)
+    for (rank_type i = 0; i < r; i++)
       s *= extents_.extent(i);
-    return s;
+    return s * scaling_;
   }
 
   template <class OtherExtents>
     requires(OtherExtents::rank() == extents_type::rank())
   friend constexpr bool operator==(const mapping& lhs, const mapping<OtherExtents>& rhs) noexcept {
-    return lhs.extents() == rhs.extents();
+    return lhs.extents() == rhs.extents() && lhs.offset_ == rhs.offset && lhs.scaling_ == rhs.scaling_;
   }
 
   friend constexpr void swap(mapping& x, mapping& y) noexcept {
@@ -320,6 +328,11 @@ class always_convertible_layout::mapping {
   }
 
 private:
+  template <class>
+  friend class mapping;
+
   extents_type extents_{};
+  index_type offset_{};
+  index_type scaling_{};
 };
-#endif                     // TEST_STD_CONTAINERS_VIEWS_MDSPAN_MDSPAN_CUSTOM_TEST_LAYOUTS_H
+#endif // TEST_STD_CONTAINERS_VIEWS_MDSPAN_CUSTOM_TEST_LAYOUTS_H
diff --git a/libcxx/test/std/containers/views/mdspan/layout_left/ctor.layout_stride.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_left/ctor.layout_stride.pass.cpp
new file mode 100644
index 000000000000000..e85270ad023db6e
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_left/ctor.layout_stride.pass.cpp
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// template<class OtherExtents>
+//   constexpr explicit(extents_type::rank() > 0)
+//     mapping(const layout_stride::mapping<OtherExtents>& other);
+//
+// Constraints: is_constructible_v<extents_type, OtherExtents> is true.
+//
+// Preconditions:
+//   - If extents_type::rank() > 0 is true, then for all r in the range [0, extents_type::rank()),
+//     other.stride(r) equals other.extents().fwd-prod-of-extents(r), and
+//   - other.required_span_size() is representable as a value of type index_type ([basic.fundamental]).
+//
+// Effects: Direct-non-list-initializes extents_ with other.extents().
+
+#include <mdspan>
+#include <type_traits>
+#include <cassert>
+#include <limits>
+
+#include "test_macros.h"
+
+template <bool implicit, class ToE, class FromE>
+constexpr void test_conversion(FromE src_exts) {
+  using To   = std::layout_left::mapping<ToE>;
+  using From = std::layout_stride::mapping<FromE>;
+  std::array<typename FromE::index_type, FromE::rank()> strides;
+  if constexpr (FromE::rank() > 0) {
+    strides[0] = 1;
+    for (size_t r = 1; r < FromE::rank(); r++)
+      strides[r] = src_exts.extent(r - 1) * strides[r - 1];
+  }
+  From src(src_exts, strides);
+
+  ASSERT_NOEXCEPT(To(src));
+  To dest(src);
+  assert(dest == src);
+
+  if constexpr (implicit) {
+    To dest_implicit = src;
+    assert(dest_implicit == src);
+  } else {
+    assert((!std::is_convertible_v<From, To>));
+  }
+}
+
+template <class T1, class T2>
+constexpr void test_conversion() {
+  constexpr size_t D = std::dynamic_extent;
+
+  // clang-format off
+  test_conversion<true,  std::extents<T1>>(std::extents<T2>());
+  test_conversion<false, std::extents<T1, D>>(std::extents<T2, D>(5));
+  test_conversion<false, std::extents<T1, 5>>(std::extents<T2, D>(5));
+  test_conversion<false, std::extents<T1, 5>>(std::extents<T2, 5>());
+  test_conversion<false, std::extents<T1, 5, D>>(std::extents<T2, D, D>(5, 5));
+  test_conversion<false, std::extents<T1, D, D>>(std::extents<T2, D, D>(5, 5));
+  test_conversion<false, std::extents<T1, D, D>>(std::extents<T2, D, 7>(5));
+  test_conversion<false, std::extents<T1, 5, 7>>(std::extents<T2, 5, 7>());
+  test_conversion<false, std::extents<T1, 5, D, 8, D, D>>(std::extents<T2, D, D, 8, 9, 1>(5, 7));
+  test_conversion<false, std::extents<T1, D, D, D, D, D>>(
+                         std::extents<T2, D, D, D, D, D>(5, 7, 8, 9, 1));
+  test_conversion<false, std::extents<T1, D, D, 8, 9, D>>(std::extents<T2, D, 7, 8, 9, 1>(5));
+  test_conversion<false, std::extents<T1, 5, 7, 8, 9, 1>>(std::extents<T2, 5, 7, 8, 9, 1>());
+  // clang-format on
+}
+
+template <class IdxT, size_t... Extents>
+using ll_mapping_t = typename std::layout_left::template mapping<std::extents<IdxT, Extents...>>;
+template <class IdxT, size_t... Extents>
+using ls_mapping_t = typename std::layout_stride::template mapping<std::extents<IdxT, Extents...>>;
+
+constexpr void test_rank_mismatch() {
+  constexpr size_t D = std::dynamic_extent;
+
+  static_assert(!std::is_constructible_v<ll_mapping_t<int, D>, ls_mapping_t<int>>);
+  static_assert(!std::is_constructible_v<ll_mapping_t<int>, ls_mapping_t<int, D, D>>);
+  static_assert(!std::is_constructible_v<ll_mapping_t<int, D>, ls_mapping_t<int, D, D>>);
+  static_assert(!std::is_constructible_v<ll_mapping_t<int, D, D, D>, ls_mapping_t<int, D, D>>);
+}
+
+constexpr void test_static_extent_mismatch() {
+  constexpr size_t D = std::dynamic_extent;
+
+  static_assert(!std::is_constructible_v<ll_mapping_t<int, D, 5>, ls_mapping_t<int, D, 4>>);
+  static_assert(!std::is_constructible_v<ll_mapping_t<int, 5>, ls_mapping_t<int, 4>>);
+  static_assert(!std::is_constructible_v<ll_mapping_t<int, 5, D>, ls_mapping_t<int, 4, D>>);
+}
+
+constexpr bool test() {
+  test_conversion<int, int>();
+  test_conversion<int, size_t>();
+  test_conversion<size_t, int>();
+  test_conversion<size_t, long>();
+  test_rank_mismatch();
+  test_static_extent_mismatch();
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_right/ctor.layout_stride.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_right/ctor.layout_stride.pass.cpp
new file mode 100644
index 000000000000000..9d9677d54011ce4
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_right/ctor.layout_stride.pass.cpp
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// template<class OtherExtents>
+//   constexpr explicit(extents_type::rank() > 0)
+//     mapping(const layout_stride::mapping<OtherExtents>& other);
+//
+// Constraints: is_constructible_v<extents_type, OtherExtents> is true.
+//
+// Preconditions:
+//   - If extents_type::rank() > 0 is true, then for all r in the range [0, extents_type::rank()),
+//     other.stride(r) equals other.extents().fwd-prod-of-extents(r), and
+//   - other.required_span_size() is representable as a value of type index_type ([basic.fundamental]).
+//
+// Effects: Direct-non-list-initializes extents_ with other.extents().
+
+#include <mdspan>
+#include <type_traits>
+#include <cassert>
+#include <limits>
+
+#include "test_macros.h"
+
+template <bool implicit, class ToE, class FromE>
+constexpr void test_conversion(FromE src_exts) {
+  using To   = std::layout_left::mapping<ToE>;
+  using From = std::layout_stride::mapping<FromE>;
+  std::array<typename FromE::index_type, FromE::rank()> strides;
+  if constexpr (FromE::rank() > 0) {
+    strides[0] = 1;
+    for (size_t r = 1; r < FromE::rank(); r++)
+      strides[r] = src_exts.extent(r - 1) * strides[r - 1];
+  }
+  From src(src_exts, strides);
+
+  ASSERT_NOEXCEPT(To(src));
+  To dest(src);
+  assert(dest == src);
+
+  if constexpr (implicit) {
+    To dest_implicit = src;
+    assert(dest_implicit == src);
+  } else {
+    assert((!std::is_convertible_v<From, To>));
+  }
+}
+
+template <class T1, class T2>
+constexpr void test_conversion() {
+  constexpr size_t D = std::dynamic_extent;
+
+  // clang-format off
+  test_conversion<true,  std::extents<T1>>(std::extents<T2>());
+  test_conversion<false, std::extents<T1, D>>(std::extents<T2, D>(5));
+  test_conversion<false, std::extents<T1, 5>>(std::extents<T2, D>(5));
+  test_conversion<false, std::extents<T1, 5>>(std::extents<T2, 5>());
+  test_conversion<false, std::extents<T1, 5, D>>(std::extents<T2, D, D>(5, 5));
+  test_conversion<false, std::extents<T1, D, D>>(std::extents<T2, D, D>(5, 5));
+  test_conversion<false, std::extents<T1, D, D>>(std::extents<T2, D, 7>(5));
+  test_conversion<false, std::extents<T1, 5, 7>>(std::extents<T2, 5, 7>());
+  test_conversion<false, std::extents<T1, 5, D, 8, D, D>>(std::extents<T2, D, D, 8, 9, 1>(5, 7));
+  test_conversion<false, std::extents<T1, D, D, D, D, D>>(
+                         std::extents<T2, D, D, D, D, D>(5, 7, 8, 9, 1));
+  test_conversion<false, std::extents<T1, D, D, 8, 9, D>>(std::extents<T2, D, 7, 8, 9, 1>(5));
+  test_conversion<false, std::extents<T1, 5, 7, 8, 9, 1>>(std::extents<T2, 5, 7, 8, 9, 1>());
+  // clang-format on
+}
+
+template <class IdxT, size_t... Extents>
+using lr_mapping_t = typename std::layout_right::template mapping<std::extents<IdxT, Extents...>>;
+template <class IdxT, size_t... Extents>
+using ls_mapping_t = typename std::layout_stride::template mapping<std::extents<IdxT, Extents...>>;
+
+constexpr void test_rank_mismatch() {
+  constexpr size_t D = std::dynamic_extent;
+
+  static_assert(!std::is_constructible_v<lr_mapping_t<int, D>, ls_mapping_t<int>>);
+  static_assert(!std::is_constructible_v<lr_mapping_t<int>, ls_mapping_t<int, D, D>>);
+  static_assert(!std::is_constructible_v<lr_mapping_t<int, D>, ls_mapping_t<int, D, D>>);
+  static_assert(!std::is_constructible_v<lr_mapping_t<int, D, D, D>, ls_mapping_t<int, D, D>>);
+}
+
+constexpr void test_static_extent_mismatch() {
+  constexpr size_t D = std::dynamic_extent;
+
+  static_assert(!std::is_constructible_v<lr_mapping_t<int, D, 5>, ls_mapping_t<int, D, 4>>);
+  static_assert(!std::is_constructible_v<lr_mapping_t<int, 5>, ls_mapping_t<int, 4>>);
+  static_assert(!std::is_constructible_v<lr_mapping_t<int, 5, D>, ls_mapping_t<int, 4, D>>);
+}
+
+constexpr bool test() {
+  test_conversion<int, int>();
+  test_conversion<int, size_t>();
+  test_conversion<size_t, int>();
+  test_conversion<size_t, long>();
+  test_rank_mismatch();
+  test_static_extent_mismatch();
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/comparison.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/comparison.pass.cpp
new file mode 100644
index 000000000000000..e624e49df5a6c45
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/comparison.pass.cpp
@@ -0,0 +1,198 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// template<class OtherMapping>
+//   friend constexpr bool operator==(const mapping& x, const OtherMapping& y) noexcept;
+//
+// Constraints:
+//   - layout-mapping-alike<OtherMapping> is satisfied.
+//   - rank_ == OtherMapping::extents_type::rank() is true.
+//   - OtherMapping::is_always_strided() is true.
+//
+// Preconditions: OtherMapping meets the layout mapping requirements ([mdspan.layout.policy.reqmts]).
+//
+// Returns: true if x.extents() == y.extents() is true, OFFSET(y) == 0 is true, and each of x.stride(r) == y.stride(r) is true for r in the range [0, x.extents().rank()). Otherwise, false.
+
+#include <mdspan>
+#include <type_traits>
+#include <concepts>
+#include <cassert>
+
+#include "test_macros.h"
+
+#include "../CustomTestLayouts.h"
+
+template <class E1, class E2>
+concept layout_mapping_comparable = requires(
+    E1 e1,
+    E2 e2,
+    std::array<typename E1::index_type, E1::rank()> s1,
+    std::array<typename E1::index_type, E1::rank()> s2) {
+  std::layout_stride::mapping<E1>(e1, s1) == std::layout_stride::mapping<E2>(e2, s2);
+};
+
+template <class T1, class T2>
+constexpr void test_comparison_different_rank() {
+  constexpr size_t D = std::dynamic_extent;
+
+  // sanity check same rank
+  static_assert(layout_mapping_comparable<std::extents<T1, D>, std::extents<T2, D>>);
+  static_assert(layout_mapping_comparable<std::extents<T1, 5>, std::extents<T2, D>>);
+  static_assert(layout_mapping_comparable<std::extents<T1, D>, std::extents<T2, 5>>);
+  static_assert(layout_mapping_comparable<std::extents<T1, 5>, std::extents<T2, 5>>);
+
+  // not equality comparable when rank is not the same
+  static_assert(!layout_mapping_comparable<std::extents<T1>, std::extents<T2, D>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1>, std::extents<T2, 1>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, D>, std::extents<T2>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, 1>, std::extents<T2>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, D>, std::extents<T2, D, D>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, 5>, std::extents<T2, 5, D>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, 5>, std::extents<T2, 5, 1>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, D, D>, std::extents<T2, D>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, 5, D>, std::extents<T2, 5>>);
+  static_assert(!layout_mapping_comparable<std::extents<T1, 5, 1>, std::extents<T2, 5>>);
+}
+
+template <class To, class From>
+constexpr void test_comparison(
+    bool equal,
+    To dest_exts,
+    From src_exts,
+    std::array<int, To::rank()> dest_strides,
+    std::array<int, From::rank()> src_strides) {
+  std::layout_stride::mapping<To> dest(dest_exts, dest_strides);
+  std::layout_stride::mapping<From> src(src_exts, src_strides);
+  ASSERT_NOEXCEPT(dest == src);
+  assert((dest == src) == equal);
+  assert((dest != src) == !equal);
+}
+
+template <class T1, class T2>
+constexpr void test_comparison_same_rank() {
+  constexpr size_t D = std::dynamic_extent;
+
+  test_comparison(true, std::extents<T1>(), std::extents<T2>(), std::array<int, 0>{}, std::array<int, 0>{});
+
+  test_comparison(true, std::extents<T1, D>(5), std::extents<T2, D>(5), std::array<int, 1>{1}, std::array<int, 1>{1});
+  test_comparison(true, std::extents<T1, D>(0), std::extents<T2, D>(0), std::array<int, 1>{1}, std::array<int, 1>{1});
+  test_comparison(true, std::extents<T1, 5>(), std::extents<T2, D>(5), std::array<int, 1>{3}, std::array<int, 1>{3});
+  test_comparison(true, std::extents<T1, D>(5), std::extents<T2, 5>(), std::array<int, 1>{1}, std::array<int, 1>{1});
+  test_comparison(true, std::extents<T1, 5>(), std::extents< T2, 5>(), std::array<int, 1>{1}, std::array<int, 1>{1});
+  test_comparison(false, std::extents<T1, 5>(), std::extents<T2, D>(5), std::array<int, 1>{2}, std::array<int, 1>{1});
+  test_comparison(false, std::extents<T1, D>(5), std::extents<T2, D>(5), std::array<int, 1>{2}, std::array<int, 1>{1});
+  test_comparison(false, std::extents<T1, D>(5), std::extents<T2, D>(7), std::array<int, 1>{1}, std::array<int, 1>{1});
+  test_comparison(false, std::extents<T1, 5>(), std::extents<T2, D>(7), std::array<int, 1>{1}, std::array<int, 1>{1});
+  test_comparison(false, std::extents<T1, D>(5), std::extents<T2, 7>(), std::array<int, 1>{1}, std::array<int, 1>{1});
+  test_comparison(false, std::extents<T1, 5>(), std::extents<T2, 7>(), std::array<int, 1>{1}, std::array<int, 1>{1});
+
+  test_comparison(
+      true,
+      std::extents<T1, D, D, D, D, D>(5, 6, 7, 8, 9),
+      std::extents<T2, D, D, D, D, D>(5, 6, 7, 8, 9),
+      std::array<int, 5>{2, 20, 200, 2000, 20000},
+      std::array<int, 5>{2, 20, 200, 2000, 20000});
+  test_comparison(
+      true,
+      std::extents<T1, D, 6, D, 8, D>(5, 7, 9),
+      std::extents<T2, 5, D, D, 8, 9>(6, 7),
+      std::array<int, 5>{2, 20, 200, 2000, 20000},
+      std::array<int, 5>{2, 20, 200, 2000, 20000});
+  test_comparison(
+      true,
+      std::extents<T1, 5, 6, 7, 8, 9>(5, 6, 7, 8, 9),
+      std::extents<T2, 5, 6, 7, 8, 9>(),
+      std::array<int, 5>{2, 20, 200, 2000, 20000},
+      std::array<int, 5>{2, 20, 200, 2000, 20000});
+  test_comparison(
+      false,
+      std::extents<T1, 5, 6, 7, 8, 9>(5, 6, 7, 8, 9),
+      std::extents<T2, 5, 6, 7, 8, 9>(),
+      std::array<int, 5>{2, 20, 200, 20000, 2000},
+      std::array<int, 5>{2, 20, 200, 2000, 20000});
+  test_comparison(
+      false,
+      std::extents<T1, D, D, D, D, D>(5, 6, 7, 8, 9),
+      std::extents<T2, D, D, D, D, D>(5, 6, 3, 8, 9),
+      std::array<int, 5>{2, 20, 200, 2000, 20000},
+      std::array<int, 5>{2, 20, 200, 2000, 20000});
+  test_comparison(
+      false,
+      std::extents<T1, D, 6, D, 8, D>(5, 7, 9),
+      std::extents<T2, 5, D, D, 3, 9>(6, 7),
+      std::array<int, 5>{2, 20, 200, 2000, 20000},
+      std::array<int, 5>{2, 20, 200, 2000, 20000});
+  test_comparison(
+      false,
+      std::extents<T1, 5, 6, 7, 8, 9>(5, 6, 7, 8, 9),
+      std::extents<T2, 5, 6, 7, 3, 9>(),
+      std::array<int, 5>{2, 20, 200, 2000, 20000},
+      std::array<int, 5>{2, 20, 200, 2000, 20000});
+}
+
+template <class OtherLayout, class E1, class E2, class... OtherArgs>
+constexpr void test_comparison_with(
+    bool expect_equal, E1 e1, std::array<typename E1::index_type, E1::rank()> strides, E2 e2, OtherArgs... other_args) {
+  typename std::layout_stride::template mapping<E1> map(e1, strides);
+  typename OtherLayout::template mapping<E2> other_map(e2, other_args...);
+
+  assert((map == other_map) == expect_equal);
+}
+
+template <class OtherLayout>
+constexpr void test_comparison_with() {
+  constexpr size_t D = std::dynamic_extent;
+  bool is_left_based =
+      std::is_same_v<OtherLayout, std::layout_left> || std::is_same_v<OtherLayout, always_convertible_layout>;
+  test_comparison_with<OtherLayout>(true, std::extents<int>(), std::array<int, 0>{}, std::extents<unsigned>());
+  test_comparison_with<OtherLayout>(true, std::extents<int, 5>(), std::array<int, 1>{1}, std::extents<unsigned, 5>());
+  test_comparison_with<OtherLayout>(true, std::extents<int, D>(5), std::array<int, 1>{1}, std::extents<unsigned, 5>());
+  test_comparison_with<OtherLayout>(false, std::extents<int, D>(5), std::array<int, 1>{2}, std::extents<unsigned, 5>());
+  test_comparison_with<OtherLayout>(
+      is_left_based, std::extents<int, D, D>(5, 7), std::array<int, 2>{1, 5}, std::extents<unsigned, D, D>(5, 7));
+  test_comparison_with<OtherLayout>(
+      !is_left_based, std::extents<int, D, D>(5, 7), std::array<int, 2>{7, 1}, std::extents<unsigned, D, D>(5, 7));
+  test_comparison_with<OtherLayout>(
+      false, std::extents<int, D, D>(5, 7), std::array<int, 2>{8, 1}, std::extents<unsigned, D, D>(5, 7));
+
+  if constexpr (std::is_same_v<OtherLayout, always_convertible_layout>) {
+    // test layout with strides not equal to product of extents
+    test_comparison_with<OtherLayout>(
+        true, std::extents<int, D, D>(5, 7), std::array<int, 2>{2, 10}, std::extents<unsigned, D, D>(5, 7), 0, 2);
+    // make sure that offset != 0 results in false
+    test_comparison_with<OtherLayout>(
+        false, std::extents<int, D, D>(5, 7), std::array<int, 2>{2, 10}, std::extents<unsigned, D, D>(5, 7), 1, 2);
+  }
+}
+
+template <class T1, class T2>
+constexpr void test_comparison_index_type() {
+  test_comparison_same_rank<T1, T2>();
+  test_comparison_different_rank<T1, T2>();
+  test_comparison_with<std::layout_right>();
+  test_comparison_with<std::layout_left>();
+  test_comparison_with<always_convertible_layout>();
+}
+
+constexpr bool test() {
+  test_comparison_index_type<int, int>();
+  test_comparison_index_type<int, size_t>();
+  test_comparison_index_type<size_t, int>();
+  test_comparison_index_type<size_t, long>();
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.default.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.default.pass.cpp
new file mode 100644
index 000000000000000..2fa8866058720d4
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.default.pass.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// Test default construction:
+//
+// constexpr mapping() noexcept;
+//
+//
+// Preconditions: layout_right::mapping<extents_type>().required_span_size() is representable as a value of type index_type ([basic.fundamental]).
+//
+// Effects: Direct-non-list-initializes extents_ with extents_type(), and for all d in the range [0, rank_),
+//          direct-non-list-initializes strides_[d] with layout_right::mapping<extents_type>().stride(d).
+
+#include <mdspan>
+#include <cassert>
+#include <cstdint>
+
+#include "test_macros.h"
+
+template <class E>
+constexpr void test_construction() {
+  using M = std::layout_stride::mapping<E>;
+  ASSERT_NOEXCEPT(M{});
+  M m;
+  E e;
+
+  // check correct extents are returned
+  ASSERT_NOEXCEPT(m.extents());
+  assert(m.extents() == e);
+
+  // check required_span_size()
+  typename E::index_type expected_size = 1;
+  for (typename E::rank_type r = 0; r < E::rank(); r++)
+    expected_size *= e.extent(r);
+  assert(m.required_span_size() == expected_size);
+
+  // check strides: node stride function is constrained on rank>0, e.extent(r) is not
+  auto strides = m.strides();
+  ASSERT_NOEXCEPT(m.strides());
+  if constexpr (E::rank() > 0) {
+    std::layout_right::mapping<E> m_right;
+    for (typename E::rank_type r = 0; r < E::rank(); r++) {
+      assert(m.stride(r) == m_right.stride(r));
+      assert(strides[r] == m.stride(r));
+    }
+  }
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  test_construction<std::extents<int>>();
+  test_construction<std::extents<unsigned, D>>();
+  test_construction<std::extents<unsigned, 7>>();
+  test_construction<std::extents<unsigned, 0>>();
+  test_construction<std::extents<unsigned, 7, 8>>();
+  test_construction<std::extents<int64_t, D, 8, D, D>>();
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_array.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_array.pass.cpp
new file mode 100644
index 000000000000000..f9157f8c6eeddec
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_array.pass.cpp
@@ -0,0 +1,138 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// template<class OtherIndexType>
+//  constexpr mapping(const extents_type& e, array<OtherIndexType, rank_> s) noexcept;
+//
+// Constraints:
+//    - is_convertible_v<const OtherIndexType&, index_type> is true, and
+//    - is_nothrow_constructible_v<index_type, const OtherIndexType&> is true.
+//
+// Preconditions:
+//    - s[i] > 0 is true for all i in the range [0, rank_).
+//    - REQUIRED-SPAN-SIZE(e, s) is representable as a value of type index_type ([basic.fundamental]).
+//    - If rank_ is greater than 0, then there exists a permutation P of the integers in the range [0, rank_),
+//      such that s[pi] >= s[pi_1] * e.extent(pi_1) is true for all i in the range [1, rank_), where pi is the ith element of P.
+//     Note 1: For layout_stride, this condition is necessary and sufficient for is_unique() to be true.
+//
+// Effects: Direct-non-list-initializes extents_ with e, and for all d in the range [0, rank_),
+//         direct-non-list-initializes strides_[d] with as_const(s[d]).
+
+#include <mdspan>
+#include <cassert>
+#include <cstdint>
+
+#include "test_macros.h"
+#include "../ConvertibleToIntegral.h"
+
+template <class E, class S>
+constexpr void test_construction(E e, S s) {
+  using M = std::layout_stride::mapping<E>;
+  ASSERT_NOEXCEPT(M{e, s});
+  M m(e, s);
+
+  // check correct extents are returned
+  ASSERT_NOEXCEPT(m.extents());
+  assert(m.extents() == e);
+
+  // check required_span_size()
+  typename E::index_type expected_size = 1;
+  for (typename E::rank_type r = 0; r < E::rank(); r++) {
+    if (e.extent(r) == 0) {
+      expected_size = 0;
+      break;
+    }
+    expected_size += (e.extent(r) - 1) * static_cast<typename E::index_type>(s[r]);
+  }
+  assert(m.required_span_size() == expected_size);
+
+  // check strides: node stride function is constrained on rank>0, e.extent(r) is not
+  auto strides = m.strides();
+  ASSERT_NOEXCEPT(m.strides());
+  if constexpr (E::rank() > 0) {
+    for (typename E::rank_type r = 0; r < E::rank(); r++) {
+      assert(m.stride(r) == static_cast<typename E::index_type>(s[r]));
+      assert(strides[r] == m.stride(r));
+    }
+  }
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  {
+    std::array<int, 0> s{};
+    test_construction(std::extents<int>(), s);
+  }
+  {
+    std::array<int, 1> s{1};
+    test_construction(std::extents<unsigned, D>(7), s);
+  }
+  {
+    std::array<int, 1> s{1};
+    test_construction(std::extents<unsigned, D>(0), s);
+  }
+  {
+    std::array<int, 1> s{2};
+    test_construction(std::extents<unsigned, 7>(), s);
+  }
+  {
+    std::array<IntType, 1> s{1};
+    test_construction(std::extents<int, D>(7), s);
+  }
+  {
+    std::array<int, 2> s{3, 30};
+    test_construction(std::extents<unsigned, 7, 8>(), s);
+  }
+  {
+    std::array<int, 4> s{20, 2, 200, 2000};
+    test_construction(std::extents<int64_t, D, 8, D, D>(7, 9, 10), s);
+    test_construction(std::extents<int64_t, D, 8, D, D>(0, 9, 10), s);
+    test_construction(std::extents<int64_t, D, 8, D, D>(0, 8, 0), s);
+  }
+  {
+    std::array<int, 4> s{200, 20, 20, 2000};
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 0, 8, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 8, 0, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 8, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 8, 1, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 1, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 0, 0, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 1, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 0, 9), s);
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 0, 1, 9), s);
+  }
+
+  {
+    using mapping_t = std::layout_stride::mapping<std::dextents<unsigned, 2>>;
+    // wrong strides size
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::array<int, 3>>);
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::array<int, 1>>);
+    // wrong extents rank
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 3>, std::array<int, 2>>);
+    // none-convertible strides
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::array<IntType, 2>>);
+  }
+  {
+    // not no-throw constructible index_type from stride
+    using mapping_t = std::layout_stride::mapping<std::dextents<unsigned char, 2>>;
+    static_assert(std::is_convertible_v<IntType, unsigned char>);
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::array<IntType, 2>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_span.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_span.pass.cpp
new file mode 100644
index 000000000000000..36a87ae7a9e843b
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.extents_span.pass.cpp
@@ -0,0 +1,141 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// template<class OtherIndexType>
+//  constexpr mapping(const extents_type& e, span<OtherIndexType, rank_> s) noexcept;
+//
+// Constraints:
+//    - is_convertible_v<const OtherIndexType&, index_type> is true, and
+//    - is_nothrow_constructible_v<index_type, const OtherIndexType&> is true.
+//
+// Preconditions:
+//    - s[i] > 0 is true for all i in the range [0, rank_).
+//    - REQUIRED-SPAN-SIZE(e, s) is representable as a value of type index_type ([basic.fundamental]).
+//    - If rank_ is greater than 0, then there exists a permutation P of the integers in the range [0, rank_),
+//      such that s[pi] >= s[pi_1] * e.extent(pi_1) is true for all i in the range [1, rank_), where pi is the ith element of P.
+//     Note 1: For layout_stride, this condition is necessary and sufficient for is_unique() to be true.
+//
+// Effects: Direct-non-list-initializes extents_ with e, and for all d in the range [0, rank_),
+//         direct-non-list-initializes strides_[d] with as_const(s[d]).
+
+#include <mdspan>
+#include <cassert>
+#include <cstdint>
+
+#include "test_macros.h"
+#include "../ConvertibleToIntegral.h"
+
+template <class E, class S>
+constexpr void test_construction(E e, S s) {
+  using M = std::layout_stride::mapping<E>;
+  ASSERT_NOEXCEPT(M{e, s});
+  M m(e, s);
+
+  // check correct extents are returned
+  ASSERT_NOEXCEPT(m.extents());
+  assert(m.extents() == e);
+
+  // check required_span_size()
+  typename E::index_type expected_size = 1;
+  for (typename E::rank_type r = 0; r < E::rank(); r++) {
+    if (e.extent(r) == 0) {
+      expected_size = 0;
+      break;
+    }
+    expected_size += (e.extent(r) - 1) * static_cast<typename E::index_type>(s[r]);
+  }
+  assert(m.required_span_size() == expected_size);
+
+  // check strides: node stride function is constrained on rank>0, e.extent(r) is not
+  auto strides = m.strides();
+  ASSERT_NOEXCEPT(m.strides());
+  if constexpr (E::rank() > 0) {
+    for (typename E::rank_type r = 0; r < E::rank(); r++) {
+      assert(m.stride(r) == static_cast<typename E::index_type>(s[r]));
+      assert(strides[r] == m.stride(r));
+    }
+  }
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  {
+    std::array<int, 0> s{};
+    test_construction(std::extents<int>(), std::span(s));
+  }
+  {
+    std::array<int, 1> s{1};
+    test_construction(std::extents<unsigned, D>(7), std::span(s));
+  }
+  {
+    std::array<int, 1> s{1};
+    test_construction(std::extents<unsigned, D>(0), std::span(s));
+  }
+  {
+    std::array<int, 1> s{2};
+    test_construction(std::extents<unsigned, 7>(), std::span(s));
+  }
+  {
+    std::array<IntType, 1> s{1};
+    test_construction(std::extents<int, D>(7), std::span(s));
+  }
+  {
+    std::array<int, 2> s{3, 30};
+    test_construction(std::extents<unsigned, 7, 8>(), std::span(s));
+  }
+  {
+    std::array<int, 4> s{20, 2, 200, 2000};
+    test_construction(std::extents<int64_t, D, 8, D, D>(7, 9, 10), std::span(s));
+  }
+  {
+    std::array<int, 4> s{20, 2, 200, 2000};
+    test_construction(std::extents<int64_t, D, 8, D, D>(7, 0, 10), std::span(s));
+    test_construction(std::extents<int64_t, D, 8, D, D>(0, 9, 10), std::span(s));
+    test_construction(std::extents<int64_t, D, 8, D, D>(0, 8, 0), std::span(s));
+  }
+  {
+    std::array<int, 4> s{200, 20, 20, 2000};
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 0, 8, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 8, 0, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 8, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 8, 1, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 1, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 0, 0, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 1, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 1, 0, 9), std::span(s));
+    test_construction(std::extents<int64_t, D, D, D, D>(7, 0, 1, 9), std::span(s));
+  }
+
+  {
+    using mapping_t = std::layout_stride::mapping<std::dextents<unsigned, 2>>;
+    // wrong strides size
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::span<int, 3>>);
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::span<int, 1>>);
+    // wrong extents rank
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 3>, std::span<int, 2>>);
+    // none-convertible strides
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::span<IntType, 2>>);
+  }
+  {
+    // not no-throw constructible index_type from stride
+    using mapping_t = std::layout_stride::mapping<std::dextents<unsigned char, 2>>;
+    static_assert(std::is_convertible_v<IntType, unsigned char>);
+    static_assert(!std::is_constructible_v<mapping_t, std::dextents<int, 2>, std::span<IntType, 2>>);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.strided_mapping.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.strided_mapping.pass.cpp
new file mode 100644
index 000000000000000..7c9b3f34a41f0ec
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/ctor.strided_mapping.pass.cpp
@@ -0,0 +1,187 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// template<class StridedLayoutMapping>
+//   constexpr explicit(see below)
+//     mapping(const StridedLayoutMapping& other) noexcept;
+//
+// Constraints:
+//   - layout-mapping-alike<StridedLayoutMapping> is satisfied.
+//   - is_constructible_v<extents_type, typename StridedLayoutMapping::extents_type> is true.
+//   - StridedLayoutMapping::is_always_unique() is true.
+//   - StridedLayoutMapping::is_always_strided() is true.
+//
+// Preconditions:
+//   - StridedLayoutMapping meets the layout mapping requirements ([mdspan.layout.policy.reqmts]),
+//   - other.stride(r) > 0 is true for every rank index r of extents(),
+//   - other.required_span_size() is representable as a value of type index_type ([basic.fundamental]), and
+//   - OFFSET(other) == 0 is true.
+//
+// Effects: Direct-non-list-initializes extents_ with other.extents(), and for all d in the range [0, rank_),
+//          direct-non-list-initializes strides_[d] with other.stride(d).
+//
+// Remarks: The expression inside explicit is equivalent to:
+//   - !(is_convertible_v<typename StridedLayoutMapping::extents_type, extents_type> &&
+//       (is-mapping-of<layout_left, LayoutStrideMapping> ||
+//        is-mapping-of<layout_right, LayoutStrideMapping> ||
+//        is-mapping-of<layout_stride, LayoutStrideMapping>))
+
+#include <mdspan>
+#include <type_traits>
+#include <cassert>
+#include <limits>
+
+#include "test_macros.h"
+
+#include "../CustomTestLayouts.h"
+
+template <bool implicit, class FromL, class ToE, class FromE>
+constexpr void test_conversion(FromE src_exts) {
+  using To   = std::layout_stride::mapping<ToE>;
+  using From = typename FromL::template mapping<FromE>;
+
+  From src([&]() {
+    if constexpr (std::is_same_v<FromL, std::layout_stride>) {
+      // just construct some strides which aren't layout_left/layout_right
+      std::array<size_t, FromE::rank()> strides;
+      size_t stride = 2;
+      for (size_t r = 0; r < FromE::rank(); r++) {
+        strides[r] = stride;
+        stride *= src_exts.extent(r);
+      }
+      return From(src_exts, strides);
+    } else {
+      return From(src_exts);
+    }
+  }());
+
+  ASSERT_NOEXCEPT(To(src));
+  To dest(src);
+  assert(dest == src);
+
+  if constexpr (implicit) {
+    To dest_implicit = src;
+    assert(dest_implicit == src);
+  } else {
+    assert((!std::is_convertible_v<From, To>));
+  }
+}
+
+template <class FromL, class T1, class T2>
+constexpr void test_conversion() {
+  constexpr size_t D = std::dynamic_extent;
+  constexpr bool idx_convertible =
+      static_cast<size_t>(std::numeric_limits<T1>::max()) >= static_cast<size_t>(std::numeric_limits<T2>::max());
+  constexpr bool l_convertible =
+      std::is_same_v<FromL, std::layout_right> || std::is_same_v<FromL, std::layout_left> ||
+      std::is_same_v<FromL, std::layout_stride>;
+  constexpr bool idx_l_convertible = idx_convertible && l_convertible;
+
+  // clang-format off
+  // adding extents convertibility expectation
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1>>(std::extents<T2>());
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, D>>(std::extents<T2, D>(0));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, D>>(std::extents<T2, D>(5));
+  test_conversion<idx_l_convertible && false, FromL, std::extents<T1, 5>>(std::extents<T2, D>(5));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, 5>>(std::extents<T2, 5>());
+  test_conversion<idx_l_convertible && false, FromL, std::extents<T1, 5, D>>(std::extents<T2, D, D>(5, 5));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, D, D>>(std::extents<T2, D, D>(5, 5));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, D, D>>(std::extents<T2, D, 7>(5));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, 5, 7>>(std::extents<T2, 5, 7>());
+  test_conversion<idx_l_convertible && false, FromL, std::extents<T1, 5, D, 8, D, D>>(std::extents<T2, D, D, 8, 9, 1>(5, 7));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, D, D, D, D, D>>(
+                                                     std::extents<T2, D, D, D, D, D>(5, 7, 8, 9, 1));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, D, D, 8, 9, D>>(std::extents<T2, D, 7, 8, 9, 1>(5));
+  test_conversion<idx_l_convertible && true,  FromL, std::extents<T1, 5, 7, 8, 9, 1>>(std::extents<T2, 5, 7, 8, 9, 1>());
+  // clang-format on
+}
+
+template <class IdxT, size_t... Extents>
+using ToM = typename std::layout_stride::template mapping<std::extents<IdxT, Extents...>>;
+
+template <class FromL, class IdxT, size_t... Extents>
+using FromM = typename FromL::template mapping<std::extents<IdxT, Extents...>>;
+
+template <class FromL>
+constexpr void test_no_implicit_conversion() {
+  constexpr size_t D = std::dynamic_extent;
+
+  // Sanity check that one static to dynamic conversion works
+  static_assert(std::is_constructible_v<ToM<int, D>, FromM<FromL, int, 5>>);
+  static_assert(std::is_convertible_v<FromM<FromL, int, 5>, ToM<int, D>>);
+
+  // Check that dynamic to static conversion only works explicitly
+  static_assert(std::is_constructible_v<ToM<int, 5>, FromM<FromL, int, D>>);
+  static_assert(!std::is_convertible_v<FromM<FromL, int, D>, ToM<int, 5>>);
+
+  // Sanity check that one static to dynamic conversion works
+  static_assert(std::is_constructible_v<ToM<int, D, 7>, FromM<FromL, int, 5, 7>>);
+  static_assert(std::is_convertible_v<FromM<FromL, int, 5, 7>, ToM<int, D, 7>>);
+
+  // Check that dynamic to static conversion only works explicitly
+  static_assert(std::is_constructible_v<ToM<int, 5, 7>, FromM<FromL, int, D, 7>>);
+  static_assert(!std::is_convertible_v<FromM<FromL, int, D, 7>, ToM<int, 5, 7>>);
+
+  // Sanity check that smaller index_type to larger index_type conversion works
+  static_assert(std::is_constructible_v<ToM<size_t, 5>, FromM<FromL, int, 5>>);
+  static_assert(std::is_convertible_v<FromM<FromL, int, 5>, ToM<size_t, 5>>);
+
+  // Check that larger index_type to smaller index_type conversion works explicitly only
+  static_assert(std::is_constructible_v<ToM<int, 5>, FromM<FromL, size_t, 5>>);
+  static_assert(!std::is_convertible_v<FromM<FromL, size_t, 5>, ToM<int, 5>>);
+}
+
+template <class FromL>
+constexpr void test_rank_mismatch() {
+  constexpr size_t D = std::dynamic_extent;
+
+  static_assert(!std::is_constructible_v<ToM<int, D>, FromM<FromL, int>>);
+  static_assert(!std::is_constructible_v<ToM<int>, FromM<FromL, int, D, D>>);
+  static_assert(!std::is_constructible_v<ToM<int, D>, FromM<FromL, int, D, D>>);
+  static_assert(!std::is_constructible_v<ToM<int, D, D, D>, FromM<FromL, int, D, D>>);
+}
+
+template <class FromL>
+constexpr void test_static_extent_mismatch() {
+  constexpr size_t D = std::dynamic_extent;
+
+  static_assert(!std::is_constructible_v<ToM<int, D, 5>, FromM<FromL, int, D, 4>>);
+  static_assert(!std::is_constructible_v<ToM<int, 5>, FromM<FromL, int, 4>>);
+  static_assert(!std::is_constructible_v<ToM<int, 5, D>, FromM<FromL, int, 4, D>>);
+}
+
+template <class FromL>
+constexpr void test_layout() {
+  test_conversion<FromL, int, int>();
+  test_conversion<FromL, int, size_t>();
+  test_conversion<FromL, size_t, int>();
+  test_conversion<FromL, size_t, long>();
+  // the implicit convertibility test doesn't apply to non std::layouts
+  if constexpr (!std::is_same_v<FromL, always_convertible_layout>)
+    test_no_implicit_conversion<FromL>();
+  test_rank_mismatch<FromL>();
+  test_static_extent_mismatch<FromL>();
+}
+
+constexpr bool test() {
+  test_layout<std::layout_right>();
+  test_layout<std::layout_left>();
+  test_layout<std::layout_stride>();
+  test_layout<always_convertible_layout>();
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/deduction.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/deduction.pass.cpp
new file mode 100644
index 000000000000000..ebe31d9d0ece34e
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/deduction.pass.cpp
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// ADDITIONAL_COMPILE_FLAGS: -Wno-ctad-maybe-unsupported
+
+// <mdspan>
+
+#include <mdspan>
+#include <type_traits>
+#include <concepts>
+#include <cassert>
+
+#include "test_macros.h"
+
+// mdspan
+
+// layout_stride::mapping does not have explicit deduction guides,
+// but implicit deduction guides for constructor taking extents and strides
+// should work
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+
+  ASSERT_SAME_TYPE(decltype(std::layout_stride::mapping(std::extents<int>(), std::array<unsigned, 0>())),
+                   std::layout_stride::template mapping<std::extents<int>>);
+  ASSERT_SAME_TYPE(decltype(std::layout_stride::mapping(std::extents<int, 4>(), std::array<char, 1>{1})),
+                   std::layout_stride::template mapping<std::extents<int, 4>>);
+  ASSERT_SAME_TYPE(decltype(std::layout_stride::mapping(std::extents<int, D>(), std::array<char, 1>{1})),
+                   std::layout_stride::template mapping<std::extents<int, D>>);
+  ASSERT_SAME_TYPE(
+      decltype(std::layout_stride::mapping(std::extents<unsigned, D, 3>(), std::array<int64_t, 2>{3, 100})),
+      std::layout_stride::template mapping<std::extents<unsigned, D, 3>>);
+
+  ASSERT_SAME_TYPE(decltype(std::layout_stride::mapping(std::extents<int>(), std::span<unsigned, 0>())),
+                   std::layout_stride::template mapping<std::extents<int>>);
+  ASSERT_SAME_TYPE(decltype(std::layout_stride::mapping(std::extents<int, 4>(), std::declval<std::span<char, 1>>())),
+                   std::layout_stride::template mapping<std::extents<int, 4>>);
+  ASSERT_SAME_TYPE(decltype(std::layout_stride::mapping(std::extents<int, D>(), std::declval<std::span<char, 1>>())),
+                   std::layout_stride::template mapping<std::extents<int, D>>);
+  ASSERT_SAME_TYPE(
+      decltype(std::layout_stride::mapping(std::extents<unsigned, D, 3>(), std::declval<std::span<int64_t, 2>>())),
+      std::layout_stride::template mapping<std::extents<unsigned, D, 3>>);
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/extents.verify.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/extents.verify.cpp
new file mode 100644
index 000000000000000..4742527f7af11e3
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/extents.verify.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// template<class Extents>
+// class layout_stride::mapping;
+
+// If Extents is not a specialization of extents, then the program is
+// ill-formed.
+
+// Mandates: If Extents::rank_dynamic() == 0 is true, then the size of the
+// multidimensional index space Extents() is representable as a value of type
+// typename Extents::index_type.
+
+#include <mdspan>
+
+void not_extents() {
+  // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}layout_stride::mapping template argument must be a specialization of extents}}
+  [[maybe_unused]] std::layout_stride::mapping<void> mapping;
+}
+
+void representable() {
+  // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}layout_stride::mapping product of static extents must be representable as index_type.}}
+  [[maybe_unused]] std::layout_stride::mapping<std::extents<char, 20, 20>> mapping;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/index_operator.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/index_operator.pass.cpp
new file mode 100644
index 000000000000000..dbd56a54758c97c
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/index_operator.pass.cpp
@@ -0,0 +1,121 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// Test default iteration:
+//
+// template<class... Indices>
+//   constexpr index_type operator()(Indices...) const noexcept;
+//
+// Constraints:
+//   * sizeof...(Indices) == extents_type::rank() is true,
+//   * (is_convertible_v<Indices, index_type> && ...) is true, and
+//   * (is_nothrow_constructible_v<index_type, Indices> && ...) is true.
+//
+// Preconditions:
+//   * extents_type::index-cast(i) is a multidimensional index in extents_.
+
+#include <mdspan>
+#include <cassert>
+#include <cstdint>
+
+#include "test_macros.h"
+
+#include "../ConvertibleToIntegral.h"
+
+template <class Mapping, class... Indices>
+concept operator_constraints = requires(Mapping m, Indices... idxs) {
+  { std::is_same_v<decltype(m(idxs...)), typename Mapping::index_type> };
+};
+
+template <class Mapping, class... Indices>
+  requires(operator_constraints<Mapping, Indices...>)
+constexpr bool check_operator_constraints(Mapping m, Indices... idxs) {
+  (void)m(idxs...);
+  return true;
+}
+
+template <class Mapping, class... Indices>
+constexpr bool check_operator_constraints(Mapping, Indices...) {
+  return false;
+}
+
+template <class M, class... Args>
+constexpr void iterate_stride(M m, const std::array<int, M::extents_type::rank()>& strides, Args... args) {
+  constexpr int r = static_cast<int>(M::extents_type::rank()) - 1 - static_cast<int>(sizeof...(Args));
+  if constexpr (-1 == r) {
+    ASSERT_NOEXCEPT(m(args...));
+    size_t expected_val = [&]<size_t... Pos>(std::index_sequence<Pos...>) {
+      return ((args * strides[Pos]) + ... + 0);
+    }(std::make_index_sequence<M::extents_type::rank()>());
+    assert(expected_val == static_cast<size_t>(m(args...)));
+  } else {
+    for (typename M::index_type i = 0; i < m.extents().extent(r); i++) {
+      iterate_stride(m, strides, i, args...);
+    }
+  }
+}
+
+template <class E, class... Args>
+constexpr void test_iteration(std::array<int, E::rank()> strides, Args... args) {
+  using M = std::layout_stride::mapping<E>;
+  M m(E(args...), strides);
+
+  iterate_stride(m, strides);
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  test_iteration<std::extents<int>>(std::array<int, 0>{});
+  test_iteration<std::extents<unsigned, D>>(std::array<int, 1>{2}, 1);
+  test_iteration<std::extents<unsigned, D>>(std::array<int, 1>{3}, 7);
+  test_iteration<std::extents<unsigned, 7>>(std::array<int, 1>{4});
+  test_iteration<std::extents<unsigned, 7, 8>>(std::array<int, 2>{25, 3});
+  test_iteration<std::extents<char, D, D, D, D>>(std::array<int, 4>{1, 1, 1, 1}, 1, 1, 1, 1);
+
+  // Check operator constraint for number of arguments
+  static_assert(check_operator_constraints(
+      std::layout_stride::mapping<std::extents<int, D>>(std::extents<int, D>(1), std::array{1}), 0));
+  static_assert(!check_operator_constraints(
+      std::layout_stride::mapping<std::extents<int, D>>(std::extents<int, D>(1), std::array{1}), 0, 0));
+
+  // Check operator constraint for convertibility of arguments to index_type
+  static_assert(check_operator_constraints(
+      std::layout_stride::mapping<std::extents<int, D>>(std::extents<int, D>(1), std::array{1}), IntType(0)));
+  static_assert(!check_operator_constraints(
+      std::layout_stride::mapping<std::extents<unsigned, D>>(std::extents<unsigned, D>(1), std::array{1}), IntType(0)));
+
+  // Check operator constraint for no-throw-constructibility of index_type from arguments
+  static_assert(!check_operator_constraints(
+      std::layout_stride::mapping<std::extents<unsigned char, D>>(std::extents<unsigned char, D>(1), std::array{1}),
+      IntType(0)));
+
+  return true;
+}
+
+constexpr bool test_large() {
+  constexpr size_t D = std::dynamic_extent;
+  test_iteration<std::extents<int64_t, D, 8, D, D>>(std::array<int, 4>{2000, 2, 20, 200}, 7, 9, 10);
+  test_iteration<std::extents<int64_t, D, 8, 1, D>>(std::array<int, 4>{2000, 20, 20, 200}, 7, 10);
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  // The large test iterates over ~10k loop indices.
+  // With assertions enabled this triggered the maximum default limit
+  // for steps in consteval expressions. Assertions roughly double the
+  // total number of instructions, so this was already close to the maximum.
+  test_large();
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp
new file mode 100644
index 000000000000000..d13db91ac5a175d
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// constexpr bool is_exhaustive() const noexcept;
+//
+// Returns:
+//   - true if rank_ is 0.
+//   - Otherwise, true if there is a permutation P of the integers in the range [0, rank_) such that
+//     stride(p0) equals 1, and stride(pi) equals stride(pi_1) * extents().extent(pi_1) for i in the
+//     range [1, rank_), where pi is the ith element of P.
+//   - Otherwise, false.
+
+#include <mdspan>
+#include <type_traits>
+#include <concepts>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class E>
+constexpr void
+test_layout_mapping_stride(E ext, std::array<typename E::index_type, E::rank()> strides, bool exhaustive) {
+  using M = std::layout_stride::template mapping<E>;
+  M m(ext, strides);
+  assert(m.is_exhaustive() == exhaustive);
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  test_layout_mapping_stride(std::extents<int, 0>(), std::array<int, 1>{1}, true);
+  test_layout_mapping_stride(std::extents<unsigned, D>(0), std::array<unsigned, 1>{3}, false);
+  test_layout_mapping_stride(std::extents<int, 0, 3>(), std::array<int, 2>{6, 2}, true);
+  test_layout_mapping_stride(std::extents<int, D, D>(3, 0), std::array<int, 2>{6, 2}, false);
+  test_layout_mapping_stride(std::extents<int, D, D>(0, 0), std::array<int, 2>{6, 2}, false);
+  test_layout_mapping_stride(
+      std::extents<unsigned, D, D, D, D>(3, 3, 0, 3), std::array<unsigned, 4>{3, 1, 27, 9}, true);
+  test_layout_mapping_stride(std::extents<int, D, D, D, D>(0, 3, 3, 3), std::array<int, 4>{3, 1, 27, 9}, false);
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/properties.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/properties.pass.cpp
new file mode 100644
index 000000000000000..d3d0aa6b3c3a538
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/properties.pass.cpp
@@ -0,0 +1,116 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// namespace std {
+//   template<class Extents>
+//   class layout_stride::mapping {
+//
+//     ...
+//     static constexpr bool is_always_unique() noexcept { return true; }
+//     static constexpr bool is_always_exhaustive() noexcept { return false; }
+//     static constexpr bool is_always_strided() noexcept { return true; }
+//
+//     static constexpr bool is_unique() noexcept { return true; }
+//     static constexpr bool is_exhaustive() noexcept;
+//     static constexpr bool is_strided() noexcept { return true; }
+//     ...
+//   };
+// }
+//
+//
+// layout_stride::mapping<E> is a trivially copyable type that models regular for each E.
+//
+// constexpr bool is_exhaustive() const noexcept;
+//
+// Returns:
+//   - true if rank_ is 0.
+//   - Otherwise, true if there is a permutation P of the integers in the range [0, rank_) such that
+//     stride(p0) equals 1, and stride(pi) equals stride(pi_1) * extents().extent(pi_1) for i in the
+//     range [1, rank_), where pi is the ith element of P.
+//   - Otherwise, false.
+
+#include <mdspan>
+#include <type_traits>
+#include <concepts>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class E>
+constexpr void
+test_layout_mapping_stride(E ext, std::array<typename E::index_type, E::rank()> strides, bool exhaustive) {
+  using M = std::layout_stride::template mapping<E>;
+  M m(ext, strides);
+  const M c_m = m;
+  assert(m.strides() == strides);
+  assert(c_m.strides() == strides);
+  assert(m.extents() == ext);
+  assert(c_m.extents() == ext);
+  assert(M::is_unique() == true);
+  assert(m.is_exhaustive() == exhaustive);
+  assert(c_m.is_exhaustive() == exhaustive);
+  assert(M::is_strided() == true);
+  assert(M::is_always_unique() == true);
+  assert(M::is_always_exhaustive() == false);
+  assert(M::is_always_strided() == true);
+
+  ASSERT_NOEXCEPT(m.strides());
+  ASSERT_NOEXCEPT(c_m.strides());
+  ASSERT_NOEXCEPT(m.extents());
+  ASSERT_NOEXCEPT(c_m.extents());
+  ASSERT_NOEXCEPT(M::is_unique());
+  ASSERT_NOEXCEPT(m.is_exhaustive());
+  ASSERT_NOEXCEPT(c_m.is_exhaustive());
+  ASSERT_NOEXCEPT(M::is_strided());
+  ASSERT_NOEXCEPT(M::is_always_unique());
+  ASSERT_NOEXCEPT(M::is_always_exhaustive());
+  ASSERT_NOEXCEPT(M::is_always_strided());
+
+  for (typename E::rank_type r = 0; r < E::rank(); r++) {
+    assert(m.stride(r) == strides[r]);
+    assert(c_m.stride(r) == strides[r]);
+    ASSERT_NOEXCEPT(m.stride(r));
+    ASSERT_NOEXCEPT(c_m.stride(r));
+  }
+
+  typename E::index_type expected_size = 1;
+  for (typename E::rank_type r = 0; r < E::rank(); r++) {
+    if (ext.extent(r) == 0) {
+      expected_size = 0;
+      break;
+    }
+    expected_size += (ext.extent(r) - 1) * static_cast<typename E::index_type>(strides[r]);
+  }
+  assert(m.required_span_size() == expected_size);
+  assert(c_m.required_span_size() == expected_size);
+  ASSERT_NOEXCEPT(m.required_span_size());
+  ASSERT_NOEXCEPT(c_m.required_span_size());
+
+  static_assert(std::is_trivially_copyable_v<M>);
+  static_assert(std::regular<M>);
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  test_layout_mapping_stride(std::extents<int>(), std::array<int, 0>{}, true);
+  test_layout_mapping_stride(std::extents<char, 4, 5>(), std::array<char, 2>{1, 4}, true);
+  test_layout_mapping_stride(std::extents<char, 4, 5>(), std::array<char, 2>{1, 5}, false);
+  test_layout_mapping_stride(std::extents<unsigned, D, 4>(7), std::array<unsigned, 2>{20, 2}, false);
+  test_layout_mapping_stride(std::extents<size_t, D, D, D, D>(3, 3, 3, 3), std::array<size_t, 4>{3, 1, 9, 27}, true);
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/required_span_size.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/required_span_size.pass.cpp
new file mode 100644
index 000000000000000..3b6af9f6c1ff00f
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/required_span_size.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// Let REQUIRED-SPAN-SIZE(e, strides) be:
+//    - 1, if e.rank() == 0 is true,
+//    - otherwise 0, if the size of the multidimensional index space e is 0,
+//    - otherwise 1 plus the sum of products of (e.extent(r) - 1) and strides[r] for all r in the range [0, e.rank()).
+
+// constexpr index_type required_span_size() const noexcept;
+//
+//   Returns: REQUIRED-SPAN-SIZE(extents(), strides_).
+
+#include <mdspan>
+#include <cassert>
+#include <cstdint>
+
+#include "test_macros.h"
+
+template <class E>
+constexpr void test_required_span_size(E e, std::array<int, E::rank()> strides, typename E::index_type expected_size) {
+  using M = std::layout_stride::mapping<E>;
+  const M m(e, strides);
+
+  ASSERT_NOEXCEPT(m.required_span_size());
+  assert(m.required_span_size() == expected_size);
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  test_required_span_size(std::extents<int>(), std::array<int, 0>{}, 1);
+  test_required_span_size(std::extents<unsigned, D>(0), std::array<int, 1>{5}, 0);
+  test_required_span_size(std::extents<unsigned, D>(1), std::array<int, 1>{5}, 1);
+  test_required_span_size(std::extents<unsigned, D>(7), std::array<int, 1>{5}, 31);
+  test_required_span_size(std::extents<unsigned, 7>(), std::array<int, 1>{5}, 31);
+  test_required_span_size(std::extents<unsigned, 7, 8>(), std::array<int, 2>{20, 2}, 135);
+  test_required_span_size(
+      std::extents<int64_t, D, 8, D, D>(7, 9, 10), std::array<int, 4>{1, 7, 7 * 8, 7 * 8 * 9}, 5040);
+  test_required_span_size(std::extents<int64_t, 1, 8, D, D>(9, 10), std::array<int, 4>{1, 7, 7 * 8, 7 * 8 * 9}, 5034);
+  test_required_span_size(std::extents<int64_t, 1, 0, D, D>(9, 10), std::array<int, 4>{1, 7, 7 * 8, 7 * 8 * 9}, 0);
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/static_requirements.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/static_requirements.pass.cpp
new file mode 100644
index 000000000000000..112f4b9e5a04033
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/static_requirements.pass.cpp
@@ -0,0 +1,131 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// A type M meets the layout mapping requirements if
+//    - M models copyable and equality_comparable,
+//    - is_nothrow_move_constructible_v<M> is true,
+//    - is_nothrow_move_assignable_v<M> is true,
+//    - is_nothrow_swappable_v<M> is true, and
+//
+// the following types and expressions are well-formed and have the specified semantics.
+//
+//  typename M::extents_type
+//    Result: A type that is a specialization of extents.
+//
+//  typename M::index_type
+//    Result: typename M::extents_type::index_type.
+//
+//  typename M::rank_type
+//    Result: typename M::extents_type::rank_type.
+//
+//  typename M::layout_type
+//    Result: A type MP that meets the layout mapping policy requirements ([mdspan.layout.policy.reqmts]) and for which is-mapping-of<MP, M> is true.
+//
+//  m.extents()
+//    Result: const typename M::extents_type&
+//
+//  m(i...)
+//    Result: typename M::index_type
+//    Returns: A nonnegative integer less than numeric_limits<typename M::index_type>::max() and less than or equal to numeric_limits<size_t>::max().
+//
+//  m(i...) == m(static_cast<typename M::index_type>(i)...)
+//    Result: bool
+//    Returns: true
+//
+//  m.required_span_size()
+//    Result: typename M::index_type
+//    Returns: If the size of the multidimensional index space m.extents() is 0, then 0, else 1 plus the maximum value of m(i...) for all i.
+//
+//  m.is_unique()
+//    Result: bool
+//    Returns: true only if for every i and j where (i != j || ...) is true, m(i...) != m(j...) is true.
+//
+//  m.is_exhaustive()
+//    Result: bool
+//    Returns: true only if for all k in the range [0, m.required_span_size()) there exists an i such that m(i...) equals k.
+//
+//  m.is_strided()
+//    Result: bool
+//    Returns: true only if for every rank index r of m.extents() there exists an integer
+//             sr such that, for all i where (i+dr) is a multidimensional index in m.extents() ([mdspan.overview]),
+//             m((i + dr)...) - m(i...) equals sr
+//
+//  m.stride(r)
+//    Preconditions: m.is_strided() is true.
+//    Result: typename M::index_type
+//    Returns: sr as defined in m.is_strided() above.
+//
+//  M::is_always_unique()
+//    Result: A constant expression ([expr.const]) of type bool.
+//    Returns: true only if m.is_unique() is true for all possible objects m of type M.
+//
+//  M::is_always_exhaustive()
+//    Result: A constant expression ([expr.const]) of type bool.
+//    Returns: true only if m.is_exhaustive() is true for all possible objects m of type M.
+//
+//  M::is_always_strided()
+//    Result: A constant expression ([expr.const]) of type bool.
+//    Returns: true only if m.is_strided() is true for all possible objects m of type M.
+
+#include <mdspan>
+#include <type_traits>
+#include <concepts>
+#include <cassert>
+
+#include "test_macros.h"
+
+// Common requirements of all layout mappings
+template <class M, size_t... Idxs>
+void test_mapping_requirements(std::index_sequence<Idxs...>) {
+  using E = typename M::extents_type;
+  static_assert(std::__mdspan_detail::__is_extents_v<E>);
+  static_assert(std::is_copy_constructible_v<M>);
+  static_assert(std::is_nothrow_move_constructible_v<M>);
+  static_assert(std::is_nothrow_move_assignable_v<M>);
+  static_assert(std::is_nothrow_swappable_v<M>);
+  ASSERT_SAME_TYPE(typename M::index_type, typename E::index_type);
+  ASSERT_SAME_TYPE(typename M::size_type, typename E::size_type);
+  ASSERT_SAME_TYPE(typename M::rank_type, typename E::rank_type);
+  ASSERT_SAME_TYPE(typename M::layout_type, std::layout_stride);
+  ASSERT_SAME_TYPE(typename M::layout_type::template mapping<E>, M);
+  static_assert(std::is_same_v<decltype(std::declval<M>().extents()), const E&>);
+  static_assert(std::is_same_v<decltype(std::declval<M>().strides()), std::array<typename M::index_type, E::rank()>>);
+  static_assert(std::is_same_v<decltype(std::declval<M>()(Idxs...)), typename M::index_type>);
+  static_assert(std::is_same_v<decltype(std::declval<M>().required_span_size()), typename M::index_type>);
+  static_assert(std::is_same_v<decltype(std::declval<M>().is_unique()), bool>);
+  static_assert(std::is_same_v<decltype(std::declval<M>().is_exhaustive()), bool>);
+  static_assert(std::is_same_v<decltype(std::declval<M>().is_strided()), bool>);
+  static_assert(std::is_same_v<decltype(std::declval<M>().stride(0)), typename M::index_type>);
+  static_assert(std::is_same_v<decltype(M::is_always_unique()), bool>);
+  static_assert(std::is_same_v<decltype(M::is_always_exhaustive()), bool>);
+  static_assert(std::is_same_v<decltype(M::is_always_strided()), bool>);
+}
+
+template <class L, class E>
+void test_layout_mapping_requirements() {
+  using M = typename L::template mapping<E>;
+  test_mapping_requirements<M>(std::make_index_sequence<E::rank()>());
+}
+
+template <class E>
+void test_layout_mapping_stride() {
+  test_layout_mapping_requirements<std::layout_stride, E>();
+}
+
+int main(int, char**) {
+  constexpr size_t D = std::dynamic_extent;
+  test_layout_mapping_stride<std::extents<int>>();
+  test_layout_mapping_stride<std::extents<char, 4, 5>>();
+  test_layout_mapping_stride<std::extents<unsigned, D, 4>>();
+  test_layout_mapping_stride<std::extents<size_t, D, D, D, D>>();
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/layout_stride/stride.pass.cpp b/libcxx/test/std/containers/views/mdspan/layout_stride/stride.pass.cpp
new file mode 100644
index 000000000000000..07a5199e5bbb18c
--- /dev/null
+++ b/libcxx/test/std/containers/views/mdspan/layout_stride/stride.pass.cpp
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <mdspan>
+
+// constexpr index_type stride(rank_type i) const noexcept;
+//
+//   Constraints: extents_type::rank() > 0 is true.
+//
+//   Preconditions: i < extents_type::rank() is true.
+//
+//   Returns: extents().rev-prod-of-extents(i).
+
+#include <mdspan>
+#include <cassert>
+#include <array>
+#include <cstdint>
+#include <cstdio>
+#include "test_macros.h"
+
+template <class E, class... Args>
+constexpr void test_stride(std::array<typename E::index_type, E::rank()> strides, Args... args) {
+  using M = std::layout_stride::mapping<E>;
+  M m(E(args...), strides);
+
+  ASSERT_NOEXCEPT(m.stride(0));
+  for (size_t r = 0; r < E::rank(); r++)
+    assert(strides[r] == m.stride(r));
+
+  ASSERT_NOEXCEPT(m.strides());
+  auto strides_out = m.strides();
+  static_assert(std::is_same_v<decltype(strides_out), std::array<typename E::index_type, E::rank()>>);
+  for (size_t r = 0; r < E::rank(); r++)
+    assert(strides[r] == strides_out[r]);
+}
+
+constexpr bool test() {
+  constexpr size_t D = std::dynamic_extent;
+  test_stride<std::extents<unsigned, D>>(std::array<unsigned, 1>{1}, 7);
+  test_stride<std::extents<unsigned, 7>>(std::array<unsigned, 1>{1});
+  test_stride<std::extents<unsigned, 7, 8>>(std::array<unsigned, 2>{8, 1});
+  test_stride<std::extents<int64_t, D, 8, D, D>>(std::array<int64_t, 4>{720, 90, 10, 1}, 7, 9, 10);
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/assign.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/assign.pass.cpp
index a6f436fd44880c9..8741c79315f1e87 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/assign.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/assign.pass.cpp
@@ -19,7 +19,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/conversion.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/conversion.pass.cpp
index bb5927633350328..abb647e960c34e8 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/conversion.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/conversion.pass.cpp
@@ -43,7 +43,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class ToMDS, class FromMDS>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/conversion.verify.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/conversion.verify.cpp
index c9fcdb11f8db41c..e3f03ebd07e5fa0 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/conversion.verify.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/conversion.verify.cpp
@@ -37,7 +37,7 @@
 
 #include <mdspan>
 #include "CustomTestAccessors.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 
 void cant_construct_data_handle_type() {
   int data;
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.copy.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.copy.pass.cpp
index 813250d8cefff5d..7fb85ed845d6cbc 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.copy.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.copy.pass.cpp
@@ -21,7 +21,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.default.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.default.pass.cpp
index f42a4dbf1bf2c08..8ad12944df84d4b 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.default.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.default.pass.cpp
@@ -29,7 +29,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <bool hc, bool mc, bool ac, class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_array.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_array.pass.cpp
index e7fd81b3ab4e933..e4f88d6035049f6 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_array.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_array.pass.cpp
@@ -38,7 +38,7 @@
 
 #include "../ConvertibleToIntegral.h"
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class Extents, size_t... Idxs>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_extents.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_extents.pass.cpp
index c194fdaeffd1386..c15d1dc47ad9703 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_extents.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_extents.pass.cpp
@@ -31,7 +31,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <bool mec, bool ac, class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_integers.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_integers.pass.cpp
index 1bd9e488ad0d41b..0288e63106522df 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_integers.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_integers.pass.cpp
@@ -38,7 +38,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class MDS, class... Args>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map.pass.cpp
index 9b1c6bd0668af9d..9fe79ad460ccb05 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map.pass.cpp
@@ -29,7 +29,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <bool ac, class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map_acc.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map_acc.pass.cpp
index 7a94e8b04032693..3be47ae92072395 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map_acc.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_map_acc.pass.cpp
@@ -26,7 +26,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_span.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_span.pass.cpp
index f76c909783344f4..30876abea5005bb 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_span.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.dh_span.pass.cpp
@@ -38,7 +38,7 @@
 
 #include "../ConvertibleToIntegral.h"
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class Extents, size_t... Idxs>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.move.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.move.pass.cpp
index aaaa4bc85709c29..10bd5114bd4d89d 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/ctor.move.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/ctor.move.pass.cpp
@@ -21,7 +21,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp
index 69b2f7e39831afa..d83299c2c53c993 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp
@@ -55,7 +55,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp
index 9e622d1c01fc007..5af2b3a45874b5c 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp
@@ -36,7 +36,7 @@
 #include "test_macros.h"
 
 #include "../ConvertibleToIntegral.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 
 // Clang 16 does not support argument packs as input to operator []
 #if defined(__clang_major__) && __clang_major__ < 17
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/move.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/move.pass.cpp
index 544a5b8aa1ede4a..4729bd171da86a0 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/move.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/move.pass.cpp
@@ -21,7 +21,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 #include "CustomTestAccessors.h"
 
 template <class H, class M, class A>
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/properties.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/properties.pass.cpp
index c822c3ad9c24f06..8f2bc42c8c01ec1 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/properties.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/properties.pass.cpp
@@ -58,7 +58,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 
 template <class H, class M, class A>
 constexpr void test_mdspan_types(const H& handle, const M& map, const A& acc) {
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/swap.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/swap.pass.cpp
index dd1336c7af07e80..9b54da3252d47ea 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/swap.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/swap.pass.cpp
@@ -24,7 +24,7 @@
 #include "test_macros.h"
 
 #include "../MinimalElementType.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 
 template <class MDS>
 constexpr void test_swap(MDS a, MDS b) {
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/types.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/types.pass.cpp
index 5effea554c0d22d..e99f8a3c524a8bb 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/types.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/types.pass.cpp
@@ -36,7 +36,7 @@
 
 #include "../MinimalElementType.h"
 #include "CustomTestAccessors.h"
-#include "CustomTestLayouts.h"
+#include "../CustomTestLayouts.h"
 
 // Calculated expected size of an mdspan
 // Note this expectes that only default_accessor is empty
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/mdspan.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/mdspan.version.compile.pass.cpp
index e47d678b254ef8d..a44f2b6a91e5cc3 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/mdspan.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/mdspan.version.compile.pass.cpp
@@ -65,17 +65,11 @@
 
 #elif TEST_STD_VER == 23
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should be defined in c++23"
-#   endif
-#   if __cpp_lib_mdspan != 202207L
-#     error "__cpp_lib_mdspan should have the value 202207L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_mdspan
+#   error "__cpp_lib_mdspan should be defined in c++23"
+# endif
+# if __cpp_lib_mdspan != 202207L
+#   error "__cpp_lib_mdspan should have the value 202207L in c++23"
 # endif
 
 # ifdef __cpp_lib_submdspan
@@ -84,17 +78,11 @@
 
 #elif TEST_STD_VER > 23
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should be defined in c++26"
-#   endif
-#   if __cpp_lib_mdspan != 202207L
-#     error "__cpp_lib_mdspan should have the value 202207L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_mdspan
+#   error "__cpp_lib_mdspan should be defined in c++26"
+# endif
+# if __cpp_lib_mdspan != 202207L
+#   error "__cpp_lib_mdspan should have the value 202207L in c++26"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 3804a8b2bd3459d..ed86c555221c542 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -4804,17 +4804,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should be defined in c++23"
-#   endif
-#   if __cpp_lib_mdspan != 202207L
-#     error "__cpp_lib_mdspan should have the value 202207L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_mdspan
+#   error "__cpp_lib_mdspan should be defined in c++23"
+# endif
+# if __cpp_lib_mdspan != 202207L
+#   error "__cpp_lib_mdspan should have the value 202207L in c++23"
 # endif
 
 # if !defined(_LIBCPP_AVAILABILITY_HAS_NO_PMR)
@@ -6345,17 +6339,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should be defined in c++26"
-#   endif
-#   if __cpp_lib_mdspan != 202207L
-#     error "__cpp_lib_mdspan should have the value 202207L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_mdspan
-#     error "__cpp_lib_mdspan should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_mdspan
+#   error "__cpp_lib_mdspan should be defined in c++26"
+# endif
+# if __cpp_lib_mdspan != 202207L
+#   error "__cpp_lib_mdspan should have the value 202207L in c++26"
 # endif
 
 # if !defined(_LIBCPP_AVAILABILITY_HAS_NO_PMR)
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 4b114d3824d7bb8..76faa275d2d7e2f 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -689,7 +689,6 @@ def add_version_header(tc):
             "name": "__cpp_lib_mdspan",
             "values": {"c++23": 202207},
             "headers": ["mdspan"],
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_memory_resource",



More information about the libcxx-commits mailing list