On Thu, Apr 10, 2025 at 1:21 PM Tomasz Kaminski <tkami...@redhat.com> wrote:

>
>
> 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.
>
For more concrete illustration of the suggestion, here is very limited
implementation:
https://godbolt.org/z/Gq59b4x4P
I hope this help to clarify.

>
>  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