On Thu, Jul 3, 2025 at 4:14 PM Luc Grosheintz <luc.groshei...@gmail.com>
wrote:

> 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?
>
I go with anything outside a function call or concept check needs a parens.
I think that close enough.

>
> >
> >> +       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.
>
Interesting, one of motivation of padded layout was simd. From other side,
we would require existence of some additional value beyond what is
referenced.

>
> >> +      }
> >> +
> >> +      [[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