Thank you for the nice review! I've locally implemented everything and
I'll send a v3 later today or tomorrow; after squashing the commits
correctly; and retesting everything.

Meanwhile a couple of comments below.

On 7/1/25 16:42, Tomasz Kaminski wrote:
On Fri, Jun 27, 2025 at 11:37 AM Luc Grosheintz <luc.groshei...@gmail.com>
wrote:

Implements the class mdspan as described in N4950, i.e. without P3029.
It also adds tests for mdspan.

libstdc++-v3/ChangeLog:

         * include/std/mdspan (mdspan): New class.
         * src/c++23/std.cc.in: Add std::mdspan.
         * testsuite/23_containers/mdspan/class_mandate_neg.cc: New test.
         * testsuite/23_containers/mdspan/mdspan.cc: New test.
         * testsuite/23_containers/mdspan/layout_like.h: Add class
         LayoutLike which models a user-defined layout.

Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com>
---

As usual really solid implementation, few additional comments:
* use () to value-initialize in ctor initializer list
* redundant parentheses in requires clauses
* suggesting for adding __mdspan::__size
* few suggestion for tests

  libstdc++-v3/include/std/mdspan               | 282 +++++++++
  libstdc++-v3/src/c++23/std.cc.in              |   3 +-
  .../23_containers/mdspan/class_mandate_neg.cc |  58 ++
  .../23_containers/mdspan/layout_like.h        |  63 ++
  .../testsuite/23_containers/mdspan/mdspan.cc  | 540 ++++++++++++++++++
  5 files changed, 945 insertions(+), 1 deletion(-)
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc
  create mode 100644
libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h
  create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc

diff --git a/libstdc++-v3/include/std/mdspan
b/libstdc++-v3/include/std/mdspan
index e198d65bba3..852f881971e 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -1052,6 +1052,288 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        { return __p + __i; }
      };

+  namespace __mdspan
+  {
+    template<typename _Extents, typename _IndexType, size_t _Nm>
+      constexpr bool
+      __is_multi_index(const _Extents& __exts, span<_IndexType, _Nm>
__indices)
+      {
+       static_assert(__exts.rank() == _Nm);
+       for (size_t __i = 0; __i < __exts.rank(); ++__i)
+         if (__indices[__i] >= __exts.extent(__i))
+           return false;
+       return true;
+      }
+  }
+
+  template<typename _ElementType, typename _Extents,
+          typename _LayoutPolicy = layout_right,
+          typename _AccessorPolicy = default_accessor<_ElementType>>
+    class mdspan
+    {
+      static_assert(!is_array_v<_ElementType>,
+       "ElementType must not be an array type");
+      static_assert(!is_abstract_v<_ElementType>,
+       "ElementType must not be an abstract class type");
+      static_assert(__mdspan::__is_extents<_Extents>,
+       "Extents must be a specialization of std::extents");
+      static_assert(is_same_v<_ElementType,
+                             typename _AccessorPolicy::element_type>);
+
+    public:
+      using extents_type = _Extents;
+      using layout_type = _LayoutPolicy;
+      using accessor_type = _AccessorPolicy;
+      using mapping_type = typename layout_type::template
mapping<extents_type>;
+      using element_type = _ElementType;
+      using value_type = remove_cv_t<element_type>;
+      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 data_handle_type = typename accessor_type::data_handle_type;
+      using reference = typename accessor_type::reference;
+
+      static constexpr rank_type
+      rank() noexcept { return extents_type::rank(); }
+
+      static constexpr rank_type
+      rank_dynamic() noexcept { return extents_type::rank_dynamic(); }
+
+      static constexpr size_t
+      static_extent(rank_type __r) noexcept
+      { return extents_type::static_extent(__r); }
+
+      constexpr index_type
+      extent(rank_type __r) const noexcept { return
extents().extent(__r); }
+
+      constexpr
+      mdspan()
+      requires (rank_dynamic() > 0 &&
+         is_default_constructible_v<data_handle_type> &&
+         is_default_constructible_v<mapping_type> &&
+         is_default_constructible_v<accessor_type>)
+      : _M_accessor{}, _M_mapping{}, _M_handle{}

Here and in every other constructor, please use () to value-initialize the
field,
for example:
  _M_accessor(), _M_mapping(), _M_handle()

+      { }
+
+      constexpr
+      mdspan(const mdspan& __other) = default;
+
+      constexpr
+      mdspan(mdspan&& __other) = default;
+
+      template<__mdspan::__valid_index_type<index_type>... _OIndexTypes>
+       requires ((sizeof...(_OIndexTypes) == rank()
+                  || sizeof...(_OIndexTypes) == rank_dynamic())
+           && is_constructible_v<mapping_type, extents_type>
+           && is_default_constructible_v<accessor_type>)

Here, and in the whole file, the outermost parentheses are not required in
"requires".
Only one round checking of rank should remain.

Do you have a way of remembering when one does or doesn't need the extra
set of parens?


+       constexpr explicit
+       mdspan(data_handle_type __handle, _OIndexTypes... __exts)
+       : _M_accessor{},
+
  _M_mapping(_Extents(static_cast<index_type>(std::move(__exts))...)),
+         _M_handle(std::move(__handle))
+       { }
+
+      template<__mdspan::__valid_index_type<index_type> _OIndexType,
+              size_t _Nm>
+       requires ((_Nm == rank() || _Nm == rank_dynamic())
+                  && is_constructible_v<mapping_type, extents_type>
+                  && is_default_constructible_v<accessor_type>)
+       constexpr explicit(_Nm != rank_dynamic())
+       mdspan(data_handle_type __handle, span<_OIndexType, _Nm> __exts)
+       : _M_accessor{}, _M_mapping(extents_type(__exts)),
+         _M_handle(std::move(__handle))
+       { }
+
+      template<__mdspan::__valid_index_type<index_type> _OIndexType,
+              size_t _Nm>
+       requires ((_Nm == rank() || _Nm == rank_dynamic())
+                  && is_constructible_v<mapping_type, extents_type>
+                  && is_default_constructible_v<accessor_type>)
+       constexpr explicit(_Nm != rank_dynamic())
+       mdspan(data_handle_type __handle, const array<_OIndexType, _Nm>&
__exts)
+       : _M_accessor{}, _M_mapping(extents_type(__exts)),
+         _M_handle(std::move(__handle))
+       { }
+
+      constexpr
+      mdspan(data_handle_type __handle, const extents_type& __exts)
+      requires (is_constructible_v<mapping_type, const extents_type&>
+               && is_default_constructible_v<accessor_type>)
+      : _M_accessor{}, _M_mapping(__exts), _M_handle(std::move(__handle))
+      { }
+
+      constexpr
+      mdspan(data_handle_type __handle, const mapping_type& __mapping)
+      requires (is_default_constructible_v<accessor_type>)

Especially here.

+      : _M_accessor{}, _M_mapping(__mapping),
_M_handle(std::move(__handle))
+      { }
+
+      constexpr
+      mdspan(data_handle_type __handle, const mapping_type& __mapping,
+            const accessor_type& __accessor)
+      : _M_accessor(__accessor), _M_mapping(__mapping),
+        _M_handle(std::move(__handle))
+      { }
+
+      template<typename _OElementType, typename _OExtents, typename
_OLayout,
+              typename _OAccessor>
+       requires (is_constructible_v<mapping_type,
+             const typename _OLayout::mapping<_OExtents>&>
+         && is_constructible_v<accessor_type, const _OAccessor&>)
+       constexpr explicit(!(is_convertible_v<
+           const typename _OLayout::mapping<_OExtents>&, mapping_type>
+         && is_convertible_v<const _OAccessor&, accessor_type>))

I would write this as !is_convertible_v<....> ||  !is_convertible_v<....>
to match standard directly.

True, though the mdspan part seems to somewhat consistently write
the condition as `!convertible`.


+       mdspan(const mdspan<_OElementType, _OExtents, _OLayout,
_OAccessor>&
+                __other)
+       : _M_accessor(__other.accessor()), _M_mapping(__other.mapping()),
+         _M_handle(__other.data_handle())
+       {
+         static_assert(is_constructible_v<data_handle_type,
+             const typename _OAccessor::data_handle_type&>);
+         static_assert(is_constructible_v<extents_type, _OExtents>);
+       }
+
+      constexpr mdspan&
+      operator=(const mdspan& __other) = default;
+
+      constexpr mdspan&
+      operator=(mdspan&& __other) = default;
+
+      template<__mdspan::__valid_index_type<index_type>... _OIndexTypes>
+       requires (sizeof...(_OIndexTypes) == rank())
+       constexpr reference
+       operator[](_OIndexTypes... __indices) const
+       {
+         auto __checked_call = [this](auto... __idxs) -> index_type
+           {
+             if constexpr
(!__mdspan::__standardized_mapping<mapping_type>)
+               __glibcxx_assert(__mdspan::__is_multi_index(extents(),
+                 span<const index_type, sizeof...(__idxs)>({__idxs...})));
+             return _M_mapping(__idxs...);
+           };
+
+         auto __index = __checked_call(
+           static_cast<index_type>(std::move(__indices))...);
+         return _M_accessor.access(_M_handle, __index);
+       }
+
+      template<__mdspan::__valid_index_type<index_type> _OIndexType>
+       constexpr reference
+       operator[](span<_OIndexType, rank()> __indices) const
+       {
+         auto __call = [&]<size_t... _Counts>(index_sequence<_Counts...>)
+           -> reference
+           { return (*this)[index_type(as_const(__indices[_Counts]))...];
};
+         return __call(make_index_sequence<rank()>());
+       }
+
+      template<__mdspan::__valid_index_type<index_type> _OIndexType>
+       constexpr reference
+       operator[](const array<_OIndexType, rank()>& __indices) const
+       { return (*this)[span<const _OIndexType, rank()>(__indices)]; }
+
+      constexpr size_type
+      size() const noexcept
+      {
+       __glibcxx_assert(cmp_less_equal(_M_mapping.required_span_size(),
+                                       numeric_limits<size_t>::max()));
+       return size_type(__mdspan::__fwd_prod(extents(), rank()));

I wonder if we would benefit __mdspan::__size, as we know that we want to
multiple both whole
spans, and it would be also used in required_span_size() for layouts left,
right, padded (you multiply it by pad).


Yes, not having an extents.size() is inconvenient for us. This will do
nicely.

However, the required_span_size of a padded layout isn't

  pad * __size(extents)

instead it's defined as the largest linear index plus one [1]; which
is smaller by `pad - extents(k)`, where k is 0 for leftpad and rank - 1
for rightpad.

[1]: https://eel.is/c++draft/mdspan.layout#rightpad.obs-2

We'll need to remember this and likely need to ask the gurus,
because the problem is that this way we can't write SIMD loops
without peeling the last iteration. Which is a little bit sad.

+      }
+
+      [[nodiscard]] constexpr bool

Put nodiscard and return type in separate lines:
[[nodiscard]]
constexpr bool
empty()

+      empty() const noexcept
+      {
+       return __mdspan::__empty(extents());
+      }
+
+      friend constexpr void
+      swap(mdspan& __x, mdspan& __y) noexcept
+      {
+       std::swap(__x._M_mapping, __y._M_mapping);
+       std::swap(__x._M_accessor, __y._M_accessor);
+       std::swap(__x._M_handle, __y._M_handle);
+      }
+
+      constexpr const extents_type&
+      extents() const noexcept { return _M_mapping.extents(); }
+
+      constexpr const data_handle_type&
+      data_handle() const noexcept { return _M_handle; }
+
+      constexpr const mapping_type&
+      mapping() const noexcept { return _M_mapping; }
+
+      constexpr const accessor_type&
+      accessor() const noexcept { return _M_accessor; }
+
+      static constexpr bool
+      is_always_unique() { return mapping_type::is_always_unique(); }
+
+      static constexpr bool
+      is_always_exhaustive() { return
mapping_type::is_always_exhaustive(); }
+
+      static constexpr bool
+      is_always_strided() { return mapping_type::is_always_strided(); }
+
+      constexpr bool
+      is_unique() const { return _M_mapping.is_unique(); }
+
+      constexpr bool
+      is_exhaustive() const { return _M_mapping.is_exhaustive(); }
+
+      constexpr bool
+      is_strided() const { return _M_mapping. is_strided(); }
+
+      constexpr index_type
+      stride(rank_type __r) const { return _M_mapping.stride(__r); }
+
+    private:
+      [[no_unique_address]] accessor_type _M_accessor;
+      [[no_unique_address]] mapping_type _M_mapping;
+      [[no_unique_address]] data_handle_type _M_handle;
+    };
+
+  template<typename _CArray>
+    requires(is_array_v<_CArray> && rank_v<_CArray> == 1)
+    mdspan(_CArray&)
+    -> mdspan<remove_all_extents_t<_CArray>,
+             extents<size_t, extent_v<_CArray, 0>>>;
+
+  template<typename _Pointer>
+    requires(is_pointer_v<remove_reference_t<_Pointer>>)
+    mdspan(_Pointer&&)
+    -> mdspan<remove_pointer_t<remove_reference_t<_Pointer>>,
extents<size_t>>;
+
+  template<typename _ElementType, typename... _Integrals>
+    requires((is_convertible_v<_Integrals, size_t> && ...)
+             && sizeof...(_Integrals) > 0)
+    explicit mdspan(_ElementType*, _Integrals...)
+    -> mdspan<_ElementType, dextents<size_t, sizeof...(_Integrals)>>;
+
+  template<typename _ElementType, typename _OIndexType, size_t _Nm>
+    mdspan(_ElementType*, span<_OIndexType, _Nm>)
+    -> mdspan<_ElementType, dextents<size_t, _Nm>>;
+
+  template<typename _ElementType, typename _OIndexType, size_t _Nm>
+    mdspan(_ElementType*, const array<_OIndexType, _Nm>&)
+    -> mdspan<_ElementType, dextents<size_t, _Nm>>;
+
+  template<typename _ElementType, typename _IndexType, size_t...
_ExtentsPack>
+    mdspan(_ElementType*, const extents<_IndexType, _ExtentsPack...>&)
+    -> mdspan<_ElementType, extents<_IndexType, _ExtentsPack...>>;
+
+  template<typename _ElementType, typename _MappingType>
+    mdspan(_ElementType*, const _MappingType&)
+    -> mdspan<_ElementType, typename _MappingType::extents_type,
+             typename _MappingType::layout_type>;
+
+  template<typename _MappingType, typename _AccessorType>
+    mdspan(const typename _AccessorType::data_handle_type&, const
_MappingType&,
+          const _AccessorType&)
+    -> mdspan<typename _AccessorType::element_type,
+             typename _MappingType::extents_type,
+             typename _MappingType::layout_type, _AccessorType>;
+
  _GLIBCXX_END_NAMESPACE_VERSION
  }
  #endif
diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/
std.cc.in
index e692caaa5f9..b62114be467 100644
--- a/libstdc++-v3/src/c++23/std.cc.in
+++ b/libstdc++-v3/src/c++23/std.cc.in
@@ -1851,7 +1851,8 @@ export namespace std
    using std::layout_right;
    using std::layout_stride;
    using std::default_accessor;
-  // FIXME layout_left_padded, layout_right_padded, aligned_accessor and
mdspan
+  using std::mdspan;
+  // FIXME layout_left_padded, layout_right_padded, aligned_accessor,
mdsubspan
  }
  #endif

diff --git
a/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc
new file mode 100644
index 00000000000..96b9a2c41bb
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc
@@ -0,0 +1,58 @@
+// { dg-do compile { target c++23 } }
+#include<mdspan>
+
+#include <cstdint>
+#include "layout_like.h"
+
+struct ExtentsLike
+{
+  using index_type = int;
+  using size_type = unsigned int;
+  using rank_type = size_t;
+
+  static constexpr size_t rank() { return 1; }
+  static constexpr size_t rank_dynamic() { return 0; }
+};
+
+constexpr bool
+test_custom_extents_type()
+{
+  std::mdspan<double, ExtentsLike> md1; // { dg-error "required from
here" }
+  return true;
+}
+static_assert(test_custom_extents_type());
+
+constexpr bool
+test_element_type_mismatch()
+{
+  using E = std::extents<int, 1>;
+  using L = std::layout_right;
+  using A = std::default_accessor<double>;
+
+  [[maybe_unused]] std::mdspan<float, E, L, A> md2; // { dg-error
"required from here" }
+  return true;
+};
+static_assert(test_element_type_mismatch());
+
+template<typename Layout>
+constexpr bool
+test_invalid_multi_index()
+{
+
+  double data = 1.1;
+  auto m = typename Layout::mapping<std::extents<int, 1, 2, 3>>{};
+  auto md = std::mdspan(&data, m);
+
+  [[maybe_unused]] double x = md[0, 2, 2]; // { dg-error "expansion of" }
+  return true;
+};
+static_assert(test_invalid_multi_index<LayoutLike>()); // { dg-error
"expansion of" }
+static_assert(test_invalid_multi_index<std::layout_left>()); // {
dg-error "expansion of" }
+static_assert(test_invalid_multi_index<std::layout_right>()); // {
dg-error "expansion of" }
+static_assert(test_invalid_multi_index<std::layout_stride>()); // {
dg-error "expansion of" }
+
+// { dg-prune-output "Extents must be a specialization of std::extents" }
+// { dg-prune-output "no type named '_S_storage'" }
+// { dg-prune-output "non-constant condition" }
+// { dg-prune-output "static assertion failed" }
+// { dg-prune-output "__glibcxx_assert" }
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h
b/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h
new file mode 100644
index 00000000000..17ccf52c6a6
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h
@@ -0,0 +1,63 @@
+#pragma once
+
+struct LayoutLike

This is example of broadcasting layout, that returns same element for each
indices,
I would adjust functions to truly represent that. In particular, I would
make it always strided,
and return zero for each stride.

Implemented, though now it can't be used to mimic a layout
that's not strided. We have enough strided layouts, but none
that isn't. Therefore, we'd not detect if something uses
Layout::stride when Layout::is_always_strided() == false.


+{
+  template<typename Extents>
+    class 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 = LayoutLike;
+
+      constexpr
+      mapping() noexcept = default;
+
+      constexpr
+      mapping(Extents exts)
+      : m_exts(exts)
+      { }
+
+      constexpr const extents_type&
+      extents() const noexcept { return m_exts; }
+
+      constexpr index_type
+      required_span_size() const noexcept
+      { return 1; }

We should check if extents are empty here, and if so return 0,

+
+      template<typename... Indices>
+       requires (sizeof...(Indices) == extents_type::rank())
+       constexpr index_type
+       operator()(Indices...) const noexcept
+       { return 0; }
+
+      static constexpr bool
+      is_always_unique() noexcept
+      { return false; }
+
+      static constexpr bool
+      is_always_exhaustive() noexcept
+      { return false; }
+
+      static constexpr bool
+      is_always_strided() noexcept
+      { return false; }
+
+      static constexpr bool
+      is_unique() noexcept
+      { return false; }
+
+      static constexpr bool
+      is_exhaustive() noexcept
+      { return false; }
+
+      static constexpr bool
+      is_strided() noexcept
+      { return false; }
+
+    private:
+      Extents m_exts;
+    };
+};
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc
b/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc
new file mode 100644
index 00000000000..d2672878d69
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc
@@ -0,0 +1,540 @@
+// { dg-do run { target c++23 } }
+#include <mdspan>
+
+#include <testsuite_hooks.h>
+#include "extents/int_like.h"
+#include "layout_like.h"
+
+constexpr auto dyn = std::dynamic_extent;
+
+template<typename MDSpan, typename T, typename E, typename L =
std::layout_right,
+        typename A = std::default_accessor<T>>
+  constexpr void
+  assert_typedefs()
+  {
+    static_assert(std::same_as<typename MDSpan::extents_type, E>);
+    static_assert(std::same_as<typename MDSpan::layout_type, L>);
+    static_assert(std::same_as<typename MDSpan::accessor_type, A>);
+    static_assert(std::same_as<typename MDSpan::mapping_type,
+                              typename L::mapping<E>>);
+    static_assert(std::same_as<typename MDSpan::element_type, T>);
+    static_assert(std::same_as<typename MDSpan::value_type,
+                              std::remove_const_t<T>>);
+    static_assert(std::same_as<typename MDSpan::index_type,
+                              typename E::index_type>);
+    static_assert(std::same_as<typename MDSpan::size_type,
+                              typename E::size_type>);
+    static_assert(std::same_as<typename MDSpan::rank_type,
+                              typename E::rank_type>);
+    static_assert(std::same_as<typename MDSpan::data_handle_type,
+                              typename A::data_handle_type>);
+    static_assert(std::same_as<typename MDSpan::reference,
+                              typename A::reference>);
+  }
+
+template<typename T, typename E, typename L, template<typename U>
typename A>
+  constexpr void
+  test_typedefs()
+  { assert_typedefs<std::mdspan<T, E, L, A<T>>, T, E, L, A<T>>(); }
+
+constexpr void
+test_typedefs_all()
+{
+  using E = std::extents<int, 1, 2>;
+  using L = std::layout_left;
+
+  test_typedefs<double, E, L, std::default_accessor>();
+  test_typedefs<const double, E, L, std::default_accessor>();
+}
+
+template<typename MDSpan>
+  constexpr void
+  test_rank()
+  {
+    using Extents = typename MDSpan::extents_type;
+    static_assert(MDSpan::rank() == Extents::rank());
+    static_assert(MDSpan::rank_dynamic() == Extents::rank_dynamic());
+  }
+
+constexpr bool
+test_rank_all()
+{
+  test_rank<std::mdspan<double, std::extents<int>>>();
+  test_rank<std::mdspan<double, std::extents<int, 1>>>();
+  test_rank<std::mdspan<double, std::extents<int, dyn>>>();
+  return true;
+}
+
+template<typename Extents>
+  constexpr void
+  test_extent(Extents exts)
+  {
+    double data =  1.0;
+    auto md = std::mdspan(&data, exts);
+    using MDSpan = decltype(md);
+
+    for(size_t i = 0; i < MDSpan::rank(); ++i)
+      {
+       VERIFY(MDSpan::static_extent(i) == Extents::static_extent(i));
+       VERIFY(md.extent(i) == exts.extent(i));
+      }
+  }
+
+constexpr bool
+test_extent_all()
+{
+  // For rank == 0, check existence of the methods without calling them.
+  test_extent(std::extents<int>{});
+  test_extent(std::extents<int, 0>{});
+  test_extent(std::extents<int, dyn>{});
+  return true;
+}
+
+template<typename MDSpan>
+  constexpr void
+  test_class_properties()
+  {
+    static_assert(std::copyable<MDSpan>);
+    static_assert(std::is_nothrow_move_constructible_v<MDSpan>);
+    static_assert(std::is_nothrow_move_assignable_v<MDSpan>);
+    static_assert(std::is_nothrow_swappable_v<MDSpan>);
+    constexpr bool trivially_copyable =
+      std::is_trivially_copyable_v<typename MDSpan::accessor_type>
+      && std::is_trivially_copyable_v<typename MDSpan::mapping_type>
+      && std::is_trivially_copyable_v<typename MDSpan::data_handle_type>;
+    static_assert(std::is_trivially_copyable_v<MDSpan> ==
trivially_copyable);
+  }
+
+constexpr bool
+test_class_properties_all()
+{
+  test_class_properties<std::mdspan<double, std::extents<int>>>();
+  test_class_properties<std::mdspan<double, std::extents<int, 1>>>();
+  test_class_properties<std::mdspan<double, std::extents<int, dyn>>>();
+  return true;
+}
+
+constexpr bool
+test_default_ctor()
+{
+  static_assert(!std::is_default_constructible_v<std::mdspan<double,
+                                                std::extents<int>>>);
+  static_assert(!std::is_default_constructible_v<std::mdspan<double,
+                                                std::extents<int, 1>>>);
+  static_assert(std::is_default_constructible_v<std::mdspan<double,
+                                               std::extents<int, dyn>>>);
+
+  std::mdspan<double, std::extents<int, dyn>> md;
+  VERIFY(md.data_handle() == nullptr);
+  VERIFY(md.extent(0) == 0);

I would verify that mapping md.empty is true here, instead of checking
extent(0).

It doesn't feel like that's expressing directly what I'm interested in,
i.e. did the storage for dynamic extents get initialized properly.


+  return true;
+}
+
+constexpr bool
+test_from_other()
+{
+  using Extents = std::extents<int, 3, 5, 7>;
+  auto exts = Extents{};
+
+  auto mapping = std::layout_right::mapping(exts);
+  constexpr size_t n = mapping.required_span_size();
+  std::array<double, n> storage{};
+
+  auto md1 = std::mdspan(storage.data(), exts);
+  auto md2 = std::mdspan<double, std::dextents<int, 3>>(md1);
+
+  VERIFY(md1.data_handle() == md2.data_handle());
+  VERIFY(md1.size() == md2.size());
+
+  static_assert(!std::is_convertible_v<
+      std::mdspan<double, std::extents<unsigned int, 2>>,
+      std::mdspan<double, std::extents<int, 2>>>);

Check that mdspan<const double> can be constructed from mdspan<double>
here.

+
+  return true;
+}
+
+template<typename T, typename E, typename L = std::layout_right,
+        typename A = std::default_accessor<T>>
+  constexpr void
+  assert_deduced_typedefs(auto md)
+  { assert_typedefs<decltype(md), T, E, L, A>(); }
+
+constexpr bool
+test_from_carray()
+{
+  constexpr size_t n = 5;
+  double data[n] = {1.1, 2.2, 3.3, 4.4, 5.5};
+
+  auto md = std::mdspan(data);
+  assert_deduced_typedefs<double, std::extents<size_t, n>>(md);
+  VERIFY(md.rank() == 1);
+  VERIFY(md.rank_dynamic() == 0);
+  VERIFY(md[2] == data[2]);
+  return true;
+}
+
+constexpr bool
+test_from_pointer()
+{
+  double value = 12.3;
+  auto md = std::mdspan(&value);
+  assert_deduced_typedefs<double, std::extents<size_t>>(md);
+  VERIFY(md.rank() == 0);
+  VERIFY(md.rank_dynamic() == 0);
+  VERIFY(md[] == value);
+  return true;
+}
+
+constexpr bool
+test_from_pointer_and_shape()
+{
+  constexpr size_t n = 6;
+  std::array<double, n> data{1.1, 2.2, 3.3, 4.4, 5.5, 6.6};
+  std::array<int, 2> shape{2, 3};
+  std::span<const int, 2> shape_view(shape);
+
+  auto verify = [&data](auto md)
+  {
+    assert_deduced_typedefs<double, std::dextents<size_t, 2>>(md);
+    VERIFY(md.rank() == 2);
+    VERIFY(md.rank_dynamic() == 2);
+    VERIFY((md[0, 0]) == data[0]);
+    VERIFY((md[0, 1]) == data[1]);
+    VERIFY((md[1, 0]) == data[3]);
+  };
+
+  verify(std::mdspan(data.data(), shape[0], shape[1]));
+  verify(std::mdspan(data.data(), shape));
+  verify(std::mdspan(data.data(), shape_view));
+
+  std::mdspan<double, std::dextents<size_t, 2>> md1 = {data.data(),
shape};
+  verify(md1);
+
+  std::mdspan<double, std::dextents<size_t, 2>> md2 = {data.data(),
shape_view};
+  verify(md2);
+
+  static_assert(std::is_constructible_v<
+      std::mdspan<float, std::extents<int, 3, 5>>, float*>);
+  static_assert(!std::is_constructible_v<
+      std::mdspan<float, std::extents<int, 3, 5>>, float*, int>);
+  static_assert(std::is_constructible_v<
+      std::mdspan<float, std::extents<int, 3, 5>>, float*, int, int>);
+  static_assert(std::is_constructible_v<
+      std::mdspan<float, std::extents<int, 3, 5>>, float*, std::span<int,
0>>);
+  static_assert(std::is_constructible_v<
+      std::mdspan<float, std::extents<int, 3, 5>>, float*, std::span<int,
2>>);
+  static_assert(!std::is_convertible_v<
+      float*, std::mdspan<float, std::extents<int, 3, 5>>>);
+
+  static_assert(std::is_constructible_v<
+      std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int,
2>>);

I would add some mre negative test here, that you cannot construct it from
std::span<3> and can from span<1>.


To avoid a misunderstanding: Can or cannot construct from span<1>?

+  static_assert(!std::is_constructible_v<
+      std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int,
dyn>>);
+  return true;
+}
+
+constexpr bool
+test_from_extents()
+{
+  constexpr size_t n = 3*5*7;
+  std::array<double, n> storage{};
+  using Extents = std::extents<int, 3, 5, 7>;
+  auto exts = Extents{};
+  auto md = std::mdspan(storage.data(), exts);
+
+  assert_deduced_typedefs<double, Extents>(md);
+  VERIFY(md.data_handle() == storage.data());
+  VERIFY(md.extents() == exts);
+  return true;
+}
+
+constexpr bool
+test_from_mapping()
+{
+  constexpr size_t n = 3*5*7;
+  std::array<double, n> storage{};
+  using Extents = std::extents<int, 3, 5, 7>;
+
+  auto exts = Extents{};
+  auto m = std::layout_left::mapping(exts);
+  auto md = std::mdspan(storage.data(), m);
+
+  assert_deduced_typedefs<double, Extents, std::layout_left>(md);
+  VERIFY(md.data_handle() == storage.data());
+  VERIFY(md.mapping() == m);
+  return true;
+}
+
+constexpr bool
+test_from_accessor()
+{
+  constexpr size_t n = 3*5*7;
+  std::array<double, n> storage{};
+  using Extents = std::extents<int, 3, 5, 7>;
+
+  auto exts = Extents{};
+  auto m = std::layout_left::mapping(exts);
+  auto a = std::default_accessor<double>{};
+  auto md = std::mdspan(storage.data(), m, a);
+
+  assert_deduced_typedefs<double, Extents, std::layout_left>(md);
+  VERIFY(md.data_handle() == storage.data());
+  VERIFY(md.mapping() == m);
+  return true;
+}
+
+void
+test_from_int_like()
+{
+  constexpr size_t n = 3*5*7;
+  std::array<double, n> storage{};
+
+  auto verify = [&](auto md)
+    {
+      VERIFY(md.data_handle() == storage.data());
+      VERIFY(md.extent(0) == 3);
+      VERIFY(md.extent(1) == 5);
+      VERIFY(md.extent(2) == 7);
+
+      VERIFY((md[IntLike(0), 0, IntLike(0)]) == 0.0);
+      auto zero = std::array{IntLike(0), IntLike(0), IntLike(0)};
+      auto zero_view = std::span<IntLike, 3>{zero};
+      VERIFY((md[zero]) == 0.0);
+      VERIFY((md[zero_view]) == 0.0);
+    };
+
+  auto shape = std::array{IntLike(3), IntLike(5), IntLike(7)};
+  auto shape_view = std::span<IntLike, 3>{shape};
+  verify(std::mdspan(storage.data(), IntLike(3), 5, IntLike(7)));
+  verify(std::mdspan(storage.data(), shape));
+  verify(std::mdspan(storage.data(), shape_view));
+}
+
+template<typename T>
+  class OpaqueAccessor
+  {
+    struct Handle
+    {
+      T * ptr;
+    };
+
+  public:
+    using element_type = T;
+    using reference = T&;
+    using data_handle_type = Handle;
+    using offset_policy = OpaqueAccessor;
+
+    reference
+    access(data_handle_type p, size_t i) const
+    {
+      ++access_count;
+      return p.ptr[i];
+    }
+
+    typename offset_policy::data_handle_type
+    offset(data_handle_type p, size_t i) const
+    {
+      ++offset_count;
+      return typename offset_policy::data_handle_type{(void*)(p.ptr + i)};
+    }
+
+    mutable size_t access_count = 0;
+    mutable size_t offset_count = 0;
+  };
+
+void
+test_from_opaque_accessor()
+{
+  constexpr size_t n = 3*5*7;
+  std::array<double, n> storage{};
+  using Extents = std::extents<int, 3, 5, 7>;
+
+  auto exts = Extents{};
+  auto m = std::layout_left::mapping(exts);
+  auto a = OpaqueAccessor<double>{};
+  auto handle = OpaqueAccessor<double>::data_handle_type{storage.data()};
+  auto md = std::mdspan(handle, m, a);
+
+  using MDSpan = decltype(md);
+  static_assert(std::same_as<MDSpan::accessor_type, decltype(a)>);
+
+  VERIFY((md[0, 0, 0]) == 0.0);
+  VERIFY(md.accessor().access_count == 1);
+
+  VERIFY((md[2, 4, 6]) == 0.0);
+  VERIFY(md.accessor().access_count == 2);
+}
+
+template<typename T, typename Base>
+  class BaseClassAccessor
+  {
+  public:
+    using element_type = T;
+    using reference = Base&;
+    using data_handle_type = T*;
+    using offset_policy = BaseClassAccessor;
+
+    static_assert(std::common_reference_with<reference&&, element_type&>);
+
+    reference
+    access(data_handle_type p, size_t i) const
+    { return p[i]; }
+
+    typename offset_policy::data_handle_type
+    offset(data_handle_type p, size_t i) const
+    { return typename offset_policy::data_handle_type{p + i}; }
+  };
+
+struct Base
+{
+  double value = 1.0;
+};
+
+struct Derived : Base
+{
+  double value = 2.0;
+};
+
+void
+test_from_base_class_accessor()
+{
+  constexpr size_t n = 3*5*7;
+  std::array<Derived, n> storage{};
+  using Extents = std::extents<int, 3, 5, 7>;
+
+  auto exts = Extents{};
+  auto m = std::layout_left::mapping(exts);
+  auto a = BaseClassAccessor<Derived, Base>{};
+  auto md = std::mdspan(storage.data(), m, a);
+
+  using MDSpan = decltype(md);
+  static_assert(std::same_as<MDSpan::accessor_type, decltype(a)>);
+  static_assert(std::same_as<decltype(md[0, 0, 0]), Base&>);
+  VERIFY((md[0, 0, 0].value) == 1.0);
+  VERIFY((md[2, 4, 6].value) == 1.0);
+}
+
+constexpr bool
+test_from_mapping_like()
+{
+  double data = 1.1;
+  auto m = LayoutLike::mapping<std::extents<int, 1, 2, 3>>{};
+  auto md = std::mdspan(&data, m);
+  VERIFY((md[0, 0, 0]) == data);
+  VERIFY((md[0, 1, 2]) == data);
+  return true;
+}
+
+template<typename MDSpan>
+  constexpr void
+  test_empty(MDSpan md)
+  {
+    VERIFY(md.empty() == (md.size() == 0));
+  }
+
+constexpr bool
+test_empty_all()
+{
+  test_empty(std::mdspan<double, std::extents<int, dyn>>{});
+  return true;
+}
+
+constexpr bool
+test_access()
+{
+  using Extents = std::extents<int, 3, 5, 7>;
+  auto exts = Extents{};
+
+  auto mapping = std::layout_left::mapping(exts);
+  constexpr size_t n = mapping.required_span_size();
+  std::array<double, n> storage{};
+
+  auto md = std::mdspan(storage.data(), mapping);
+  static_assert(std::__mdspan::__mapping_alike<decltype(md)>);
+
+  for(int i = 0; i < exts.extent(0); ++i)
+    for(int j = 0; j < exts.extent(1); ++j)
+      for(int k = 0; k < exts.extent(2); ++k)
+       {
+         std::array<int, 3> ijk{i, j, k};
+         storage[mapping(i, j, k)] = 1.0;
+         VERIFY((md[i, j, k]) == 1.0);
+         VERIFY((md[ijk]) == 1.0);
+         VERIFY((md[std::span(ijk)]) == 1.0);
+         storage[mapping(i, j, k)] = 0.0;
+       }
+  return true;
+}
+
+constexpr bool
+test_swap()
+{
+  using Extents = std::dextents<int, 2>;
+  auto e1 = Extents{3, 5};
+  auto e2 = Extents{7, 11};
+
+  std::array<double, 3*5> s1{};
+  std::array<double, 7*11> s2{};
+
+  auto md1 = std::mdspan(s1.data(), e1);
+  auto md2 = std::mdspan(s2.data(), e2);
+
+  std::swap(md1, md2);
+
+  VERIFY(md1.data_handle() == s2.data());
+  VERIFY(md2.data_handle() == s1.data());
+
+  VERIFY(md1.size() == s2.size());
+  VERIFY(md2.size() == s1.size());
+  return true;
+}
+
+int
+main()
+{
+  test_typedefs_all();
+
+  test_rank_all();
+  test_extent_all();
+  static_assert(test_extent_all());
+
+  test_class_properties_all();
+  static_assert(test_class_properties_all());
+
+  test_empty_all();
+  static_assert(test_empty_all());
+
+  test_default_ctor();
+  static_assert(test_default_ctor());
+
+  test_from_other();
+  static_assert(test_from_other());
+
+  test_from_carray();
+  static_assert(test_from_carray());
+
+  test_from_pointer_and_shape();
+  static_assert(test_from_pointer_and_shape());
+
+  test_from_extents();
+  static_assert(test_from_extents());
+
+  test_from_mapping();
+  static_assert(test_from_mapping());
+
+  test_from_accessor();
+  static_assert(test_from_accessor());
+
+  test_from_int_like();
+  test_from_opaque_accessor();
+  test_from_base_class_accessor();
+  test_from_mapping_like();
+  static_assert(test_from_mapping_like());
+
+  test_access();
+  static_assert(test_access());
+
+  test_swap();
+  static_assert(test_swap());
+  return 0;
+}
--
2.49.0




Reply via email to