On Wed, Apr 9, 2025 at 9:28 AM Luc Grosheintz <luc.groshei...@gmail.com>
wrote:

> This implements std::extents from <mdspan> according to N4950 and
> contains partial progress towards PR107761.
>
> If an extent changes its type, there's a precondition in the standard,
> that the value is representable in the target integer type. This commit
> uses a static_cast for all of these conversions, without any additional
> checks.
>
> The precondition for 'extents::{static_,}extent' is that '__r < rank()'.
> For extents<T> this precondition is always violated and results in
> calling __builtin_trap. For all other specializations it's checked via
> __glibcxx_assert.
>
>         PR libstdc++/107761
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/mdspan (extents): New class.
>         * src/c++23/std.cc.in: Add 'using std::extents'.
>
> Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com>
> ---
>
Thanks for working on this. One general comment, as this is c++23 feature,
we can benefit for the extensions to consteval functions, and replace the
usual metaprogramming
recursion with consteval functions, like following:
__static_extent(size_t count, initializer_list<size_t> extends)
{
   // use normal for to compute the product
}
We will elminiate instantiotns of the class template in this way.

 libstdc++-v3/include/std/mdspan  | 400 +++++++++++++++++++++++++++++++
>  libstdc++-v3/src/c++23/std.cc.in |   6 +-
>  2 files changed, 405 insertions(+), 1 deletion(-)
>
> diff --git a/libstdc++-v3/include/std/mdspan
> b/libstdc++-v3/include/std/mdspan
> index 4094a416d1e..5c6a1868d62 100644
> --- a/libstdc++-v3/include/std/mdspan
> +++ b/libstdc++-v3/include/std/mdspan
> @@ -33,6 +33,10 @@
>  #pragma GCC system_header
>  #endif
>
> +#include <span>
> +#include <array>
> +#include <type_traits>
> +
>  #define __glibcxx_want_mdspan
>  #include <bits/version.h>
>
> @@ -42,6 +46,402 @@ namespace std _GLIBCXX_VISIBILITY(default)
>  {
>  _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> +  namespace __detail
>
I would use a dedicated internal namespace for mdspan, like __mdspan.

> +  {
> +    // Computed as: sum_i (i == __r) * E_i
> +    template<size_t _Count, size_t... _Extents>
> +      struct _StaticExtent;
> +
> +    template<size_t _Count, size_t _Extent, size_t... _Extents>
> +      struct _StaticExtent<_Count, _Extent, _Extents...>
> +      {
> +       static constexpr size_t
> +       _M_value(size_t __r) noexcept
> +       {
> +         return (_Count == __r) * _Extent
> +                + _StaticExtent<_Count + 1, _Extents...>::_M_value(__r);
> +       }
> +      };
> +
> +    template<size_t _Count>
> +      struct _StaticExtent<_Count>
> +      {
> +       static constexpr size_t
> +       _M_value(size_t __r) noexcept
> +       { return 0; }
> +      };
>
As mentioned, I would experiment with implementing this as a consteval
function, that accepts _Count and
extends as parameters. We could also declare them as static methods inside
the extends class itself,
and create a stack array of extends inside them:
static consteval
_static_extent(size_t i)
{
  size_t __exts[]{ __Extents };
  return __exts[i];
}
Or I suggest below, we can pass extends as std::array NTTP and have access
to it direclty.

> +
> +    // Computed as: sum_i (i < __r) * (E_i == dynamic_extent)
> +    template<size_t _Count, size_t... _Extents>
> +      struct _DynamicIndex;
> +
> +    template<size_t _Count, size_t _Extent, size_t... _Extents>
> +      struct _DynamicIndex<_Count, _Extent, _Extents...>
> +      {
> +       static constexpr size_t
> +       _M_value(size_t __r) noexcept
> +       {
> +         return (_Count < __r) * (_Extent == dynamic_extent)
> +                + _DynamicIndex<_Count + 1, _Extents...>::_M_value(__r);
> +       }
> +      };
> +
> +    template<size_t _Count>
> +      struct _DynamicIndex<_Count>
> +      {
> +       static constexpr size_t
> +       _M_value(size_t __r) noexcept
> +       { return 0; }
> +      };
> +
> +    // Computed by: counting the number of dynamic extents (_Dr); and
> returning
> +    // the static index first time `__di == _Dr`.
> +    template<size_t _Si, size_t _Dr, size_t... _Extents>
> +      struct _DynamicIndexInv;
> +
> +    template<size_t _Si, size_t _Dr, size_t _Extent, size_t... _Extents>
> +      struct _DynamicIndexInv<_Si, _Dr, _Extent, _Extents...>
> +      {
> +       static constexpr size_t
> +       _M_value(size_t __di) noexcept
> +       {
> +         if (_Extent == dynamic_extent && __di == _Dr)
> +           return _Si;
> +         else
> +           return _DynamicIndexInv<_Si+1, _Dr + (_Extent ==
> dynamic_extent),
> +                                   _Extents...>
> +                  ::_M_value(__di);
> +       }
> +      };
> +
> +    template<size_t _Si, size_t _Dr, size_t _Extent>
> +      struct _DynamicIndexInv<_Si, _Dr, _Extent>
> +      {
> +       static constexpr size_t
> +       _M_value(size_t __di) noexcept
> +       {
> +         __glibcxx_assert(__di == _Dr);
> +         return _Si;
> +       }
> +      };
> +
> +    // Aim: __ext[i] for template parameter packs.
> +    template<size_t _Count, typename... _IndexTypes>
> +      struct _GetPackElement;
> +
> +    template<size_t _Count, typename _IndexType, typename... _IndexTypes>
> +      struct _GetPackElement<_Count, _IndexType, _IndexTypes...>
> +      {
> +       template<size_t _Index>
> +         static constexpr const auto&
> +         get(const _IndexType& __current, const _IndexTypes&... __rest)
> +         {
> +           if constexpr (_Index == _Count)
> +             return __current;
> +           else
> +             return _GetPackElement<_Count+1, _IndexTypes...>
> +                    ::template get<_Index>(__rest...);
> +         }
> +      };
> +
> +    template<size_t _Di, typename... _IndexTypes>
> +      constexpr const auto&
> +      __get_element(const _IndexTypes&... __exts)
> +      {
> +       return _GetPackElement<0, _IndexTypes...>
> +              ::template get<_Di>(__exts...);
> +      }
> +
> +    template<size_t... _Extents>
> +      struct _DynamicRank
> +      {
> +       static constexpr size_t _S_value = ((_Extents == dynamic_extent) +
> ...);
> +      };
>
You can use a binary fold, and eliminate the need for separate
specialization.

> +
> +    template<>
> +      struct _DynamicRank<>
> +      {
> +       static constexpr size_t _S_value = 0;
> +      };
>
+
> +    template<typename _IndexType, typename _DynamicIndexes, typename
> _Indexes,
> +            size_t... _Extents>
> +      struct _ExtentsStorage;
> +
> +    template<typename _IndexType, size_t... _DynamicIndexes, size_t...
> _Indexes,
> +            size_t... _Extents>
> +      struct _ExtentsStorage<_IndexType,
> index_sequence<_DynamicIndexes...>,
> +                            index_sequence<_Indexes...>, _Extents...>
>
C++20 supports non-class types as template parameters, including std::array.
Thus I would consider using std::array<size_t, sizeof...(_Extends)>  as one
here.

> +      {
> +       static constexpr size_t _S_rank = sizeof...(_Extents);
> +
> +       static constexpr size_t _S_rank_dynamic =
> +         _DynamicRank<_Extents...>::_S_value;
>
I would inline the computation here.

> +
> +       static constexpr array<size_t, _S_rank_dynamic>
> _S_dynamic_index_inv{
> +         _DynamicIndexInv<0, 0, _Extents...>
> ::_M_value(_DynamicIndexes)...};
> +
> +       static constexpr array<size_t, _S_rank> _S_dynamic_index {
> +         _DynamicIndex<0, _Extents...>::_M_value(_Indexes)...};
> +
> +       template<typename _Tp>
> +         static constexpr _IndexType
> +         _M_int_cast(const _Tp& __other)
> +         { return __other; }
>
As we check std::is_nothrow_constructible_v
<http://en.cppreference.com/w/cpp/types/is_constructible><IndexType,
OtherIndexType>, we should use an explicit conversion
here: return _IndexType(__other);. And also mark this function as noexcept.

> +
> +       constexpr _ExtentsStorage() = default;
> +
> +       template<typename _OIndexType, typename _ODynamicIndexes,
> +                typename _OIndexes, size_t... _OExtents>
> +         constexpr _ExtentsStorage(
> +           const _ExtentsStorage<_OIndexType, _ODynamicIndexes, _OIndexes,
> +                                 _OExtents...>& __other)
> +         : _M_dynamic_extents{_M_int_cast(__other._M_extent(
> +             _S_dynamic_index_inv[_DynamicIndexes]))...}
> +         { }
> +
> +       template<size_t _Di, typename... _OIndexTypes>
> +         static constexpr const auto&
> +         _M_get_dynamic_element(const _OIndexTypes&... __exts)
> +         { return __get_element<_S_dynamic_index_inv[_Di]>(__exts...); }
> +
> +       template<typename... _OIndexTypes>
> +         requires (sizeof...(_OIndexTypes) == _S_rank_dynamic)
> +                  && (is_convertible_v<_OIndexTypes, _IndexType> && ...)
> +         constexpr
> +         _ExtentsStorage(const _OIndexTypes&... __exts)
> +         : _M_dynamic_extents{_M_int_cast(
> +             __get_element<_DynamicIndexes>(__exts...))...}
> +         { }
>
As `_ExtentsStorage` is built only by the extents class,  I would consider
having only
following constructors available here. The index type is required to be
integer type,
so copying it is cheap. As such, so I do not think we need to use the
integer_sequence trick here.

constexpr _ExtentsStorage(span<OhterIndexTypes, _S_rank_dynamic> __ext)
{
  auto __out = _M_dynamic_extents.data();
  for (std::size_t __i = 0; i < _S_rank; ++_i, ++_out)
    __out = __ext[i];
}
constexpr _ExtentsStorage(span<OhterIndexTypes, _S_rank> __ext)
  requires (_S_rank != _S_rank_dynamic)
{
    auto __out = _M_dynamic_extents.data();
   for (std::size_t __i = 0; i < _S_rank; ++_i)
      if (_S_rank_dynamic(i) == dynamic_extent)
      {
         __out = __ext[i];
        ++__out;
      }
}

For array and span extends constructors they can be passed directly.
For the one accepting const _OIndexTypes&... __exts, you can implement it
as:
_ExtendsStorage(span<index_type,
sizeof...(_OIndexTypes)>(initializer_list<index_type>{
_S_index_cast(_exts)...}))

+
> +       template<typename... _OIndexTypes>
> +         requires (sizeof...(_OIndexTypes) != _S_rank_dynamic)
> +                  && (is_convertible_v<_OIndexTypes, _IndexType> && ...)
> +         constexpr
> +         _ExtentsStorage(const _OIndexTypes&... __exts)
> +         : _M_dynamic_extents{_M_int_cast(
> +             _M_get_dynamic_element<_DynamicIndexes>(__exts...))...}
> +         { }
> +
> +       template<typename _OIndexType, size_t _Nm>
> +         requires (_Nm == _S_rank_dynamic)
> +         constexpr
> +         _ExtentsStorage(const span<_OIndexType, _Nm>& __exts)
> +         : _M_dynamic_extents{_M_int_cast(
> +             as_const(__exts[_DynamicIndexes]))...}
> +         { }
> +
> +       template<typename _OIndexType, size_t _Nm>
> +         requires (_Nm != _S_rank_dynamic)
> +         constexpr _ExtentsStorage(const span<_OIndexType, _Nm>& __exts)
> +         : _M_dynamic_extents{_M_int_cast(
> +             as_const(__exts[_S_dynamic_index_inv[_DynamicIndexes]]))...}
> +         { }
> +
> +       template<typename _OIndexType, size_t _Nm>
> +         requires (_Nm == _S_rank_dynamic)
> +         constexpr
> +         _ExtentsStorage(const array<_OIndexType, _Nm>& __exts)
> +         : _M_dynamic_extents{_M_int_cast(
> +             as_const(__exts[_DynamicIndexes]))...}
> +         { }
> +
> +       template<typename _OIndexType, size_t _Nm>
> +         requires (_Nm != _S_rank_dynamic)
> +         constexpr _ExtentsStorage(const array<_OIndexType, _Nm>& __exts)
> +         : _M_dynamic_extents{_M_int_cast(
> +             as_const(__exts[_S_dynamic_index_inv[_DynamicIndexes]]))...}
> +         { }
> +
> +       static constexpr size_t
> +       _M_static_extent(size_t __r) noexcept
> +       { return _StaticExtent<0, _Extents...>::_M_value(__r); }
> +
> +       constexpr _IndexType
> +       _M_extent(size_t __r) const noexcept
> +       {
> +         auto __se = _M_static_extent(__r);
> +         if (__se == dynamic_extent)
> +           return _M_dynamic_extents[_S_dynamic_index[__r]];
> +         else
> +           return __se;
> +       }
> +      private:
> +       [[no_unique_address]]
> +       array<_IndexType, _S_rank_dynamic> _M_dynamic_extents;
> +      };
> +
> +    template<typename _IndexType, size_t... _Extents>
> +      using __extents_storage = _ExtentsStorage<
> +       _IndexType,
> make_index_sequence<_DynamicRank<_Extents...>::_S_value>,
> +       make_index_sequence<sizeof...(_Extents)>, _Extents...>;
> +  }
> +
> +  template<typename _IndexType, size_t... _Extents>
> +    class extents;
> +
> +  template<typename _SIndexType, size_t... _SExtents, typename
> _OIndexType,
> +          size_t... _OExtents>
> +    constexpr bool
> +    operator==(const extents<_SIndexType, _SExtents...>& __self,
> +              const extents<_OIndexType, _OExtents...>& __other) noexcept;
> +
> +  template<typename _IndexType, size_t... _Extents>
> +    class extents
> +    {
> +      static_assert(is_integral_v<_IndexType>, "_IndexType must be
> integral.");
> +
> +    public:
> +      using index_type = _IndexType;
> +      using size_type = make_signed_t<index_type>;
> +      using rank_type = size_t;
> +
> +      static constexpr rank_type
> +      rank() noexcept { return _ExtentsStorage::_S_rank; }
> +
> +      static constexpr rank_type
> +      rank_dynamic() noexcept { return _ExtentsStorage::_S_rank_dynamic; }
> +
> +      static constexpr size_t
> +      static_extent(rank_type __r) noexcept
> +      {
> +       if constexpr (rank() == 0)
> +         __builtin_trap();
> +
> +       __glibcxx_assert(__r < rank());
> +       return _ExtentsStorage::_M_static_extent(__r);
> +      }
> +
> +      constexpr index_type
> +      extent(rank_type __r) const noexcept
> +      {
> +       if constexpr (rank() == 0)
> +         __builtin_trap();
> +
> +       __glibcxx_assert(__r < rank());
> +       return _M_extents._M_extent(__r);
> +      }
> +
> +      constexpr
> +      extents() noexcept = default;
> +
> +    private:
> +      static constexpr bool
> +      _M_is_less_dynamic(size_t __ext, size_t __oext)
> +      { return (__ext != dynamic_extent) && (__oext == dynamic_extent); }
> +
> +      template<typename _Tp>
> +       static constexpr size_t
> +       _M_limits_max() { return numeric_limits<_Tp>::max(); }
> +
> +      template<typename _OIndexType, size_t... _OExtents>
> +       static constexpr bool
> +       _M_ctor_explicit()
> +       {
> +         return (_M_is_less_dynamic(_Extents, _OExtents) || ...)
> +           || (_M_limits_max<index_type>() <
> _M_limits_max<_OIndexType>());
> +       }
> +
> +    public:
> +      template<typename _OIndexType, size_t... _OExtents>
> +       requires (sizeof...(_OExtents) == rank())
> +                && ((_OExtents == dynamic_extent || _Extents ==
> dynamic_extent
> +                     || _OExtents == _Extents) && ...)
> +       constexpr explicit(_M_ctor_explicit<_OIndexType, _OExtents...>())
> +       extents(const extents<_OIndexType, _OExtents...>& __other) noexcept
> +       : _M_extents(__other._M_extents)
> +       { }
> +
> +      template<typename... _OIndexTypes>
> +       requires (
> +         ((sizeof...(_OIndexTypes) == rank()
> +           || sizeof...(_OIndexTypes) == rank_dynamic()))
> +         && (is_convertible_v<_OIndexTypes, index_type> && ...)
> +         && (is_nothrow_constructible_v<index_type, _OIndexTypes> && ...))
> +       constexpr explicit extents(_OIndexTypes... __exts) noexcept
> +       : _M_extents(__exts...)
> +       { }
> +
> +      template<typename _OIndexType, size_t _Nm>
> +       requires (is_convertible_v<const _OIndexType& , index_type>
> +                && is_nothrow_constructible_v<index_type, const
> _OIndexType&>
> +                && (_Nm == rank_dynamic() || _Nm == rank()))
> +       constexpr explicit(_Nm != rank_dynamic())
> +       extents(span<_OIndexType, _Nm> __exts) noexcept
> +       : _M_extents(__exts)
> +       { }
> +
> +      template<typename _OIndexType, size_t _Nm>
> +       requires (is_convertible_v<const _OIndexType& , index_type>
> +                && is_nothrow_constructible_v<index_type, const
> _OIndexType&>
> +                && (_Nm == rank_dynamic() || _Nm == rank()))
> +       constexpr explicit(_Nm != rank_dynamic())
> +       extents(const array<_OIndexType, _Nm>& __exts) noexcept
> +       : _M_extents(__exts)
> +       { }
> +
> +      template<typename _SIndexType, size_t... _SExtents, typename
> _OIndexType,
> +              size_t... _OExtents>
> +       friend constexpr bool
> +       operator==(const extents&,
> +                  const extents<_OIndexType, _OExtents...>&) noexcept;
> +
> +    private:
> +      using _ExtentsStorage =
> +       typename __detail::__extents_storage<_IndexType, _Extents...>;
> +
> +      [[no_unique_address]] _ExtentsStorage _M_extents;
> +
> +      template<typename _OIndexType, size_t... _OExtents>
> +      friend class extents;
> +    };
> +
> +  template<typename _SIndexType, size_t... _SExtents, typename
> _OIndexType,
> +          size_t... _OExtents>
> +    constexpr bool
> +    operator==(const extents<_SIndexType, _SExtents...>& __self,
> +              const extents<_OIndexType, _OExtents...>& __other) noexcept
> +    {
> +      if constexpr (__self.rank() != __other.rank())
> +        return false;
> +
> +      for (size_t __i = 0; __i < __self.rank(); ++__i)
> +       if (__self.extent(__i) != __other.extent(__i))
> +         return false;
> +      return true;
> +    }
> +
> +  namespace __detail
> +  {
> +    template<typename _IndexType, size_t _Count, size_t _Rank,
> +            size_t... _Extents>
> +      struct _Dextents
> +      {
> +       using type = typename _Dextents<_IndexType, _Count+1, _Rank,
> +                                       _Extents..., dynamic_extent>::type;
> +      };
> +
> +    template<typename _IndexType, size_t _Rank, size_t... _Extents>
> +      struct _Dextents<_IndexType, _Rank, _Rank, _Extents...>
> +      {
> +       using type = extents<_IndexType, _Extents...>;
> +      };
> +
> +    template<typename _Tp>
> +      struct _DynamicExtent
> +      {
> +       static constexpr size_t _S_dyn = dynamic_extent;
> +      };
>
I would replace this by consteval function. Again each instantiation
generates a separate type.

> +  }
> +
> +  template<typename _IndexType, size_t _Rank>
> +    using dextents = typename __detail::_Dextents<_IndexType, 0,
> _Rank>::type;
> +
> +  template<typename... _Integrals>
> +    requires (is_convertible_v<_Integrals, size_t> && ...)
> +    explicit extents(_Integrals...) ->
> +      extents<size_t, __detail::_DynamicExtent<_Integrals>::_S_dyn...>;
> +
>  _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 12253b95c5a..481e39b2821 100644
> --- a/libstdc++-v3/src/c++23/std.cc.in
> +++ b/libstdc++-v3/src/c++23/std.cc.in
> @@ -1825,7 +1825,11 @@ export namespace std
>    }
>  }
>
> -// FIXME <mdspan>
> +// <mdspan>
> +{
> +  using std::extents;
> +  // FIXME layout_*, default_accessor and mdspan
> +}
>
>  // 20.2 <memory>
>  export namespace std
> --
> 2.49.0
>
>

Reply via email to