On Thu, Apr 17, 2025 at 6:07 PM Luc Grosheintz <luc.groshei...@gmail.com>
wrote:

>
> On 4/17/25 2:31 PM, Tomasz Kaminski wrote:
>
>
>
> On Thu, Apr 17, 2025 at 2:21 PM Tomasz Kaminski <tkami...@redhat.com>
> wrote:
>
>>
>>
>> On Thu, Apr 17, 2025 at 1:44 PM Tomasz Kaminski <tkami...@redhat.com>
>> wrote:
>>
>>>
>>>
>>> On Thu, Apr 17, 2025 at 1:18 PM 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
>>>> precondition is not checked at runtime.
>>>>
>>>> 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>
>>>>
>>> Looks really solid. Few very minor suggestions regarding naming, and use
>>> of
>>> consteval where function is not needed at runtime.
>>>
>>>> ---
>>>>  libstdc++-v3/include/std/mdspan  | 249 +++++++++++++++++++++++++++++++
>>>>  libstdc++-v3/src/c++23/std.cc.in |   6 +-
>>>>  2 files changed, 254 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/libstdc++-v3/include/std/mdspan
>>>> b/libstdc++-v3/include/std/mdspan
>>>> index 4094a416d1e..755c077f89c 100644
>>>> --- a/libstdc++-v3/include/std/mdspan
>>>> +++ b/libstdc++-v3/include/std/mdspan
>>>> @@ -33,6 +33,11 @@
>>>>  #pragma GCC system_header
>>>>  #endif
>>>>
>>>> +#include <span>
>>>> +#include <array>
>>>> +#include <type_traits>
>>>> +#include <limits>
>>>> +
>>>>  #define __glibcxx_want_mdspan
>>>>  #include <bits/version.h>
>>>>
>>>> @@ -41,6 +46,250 @@
>>>>  namespace std _GLIBCXX_VISIBILITY(default)
>>>>  {
>>>>  _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>>> +  namespace __mdspan
>>>> +  {
>>>> +    template<typename _IndexType, array _Extents>
>>>> +      class _ExtentsStorage
>>>> +      {
>>>> +      public:
>>>> +       static constexpr bool
>>>>
>>> +       _M_is_dyn(size_t __ext) noexcept
>>>>
>>> We use `_S_` prefix for static member functions, and `_M_` for instance
>>> member functions.
>>>
>>>> +       { return __ext == dynamic_extent; }
>>>> +
>>>> +       template<typename _OIndexType>
>>>> +         static constexpr _IndexType
>>>> +         _M_int_cast(const _OIndexType& __other) noexcept
>>>> +         { return _IndexType(__other); }
>>>> +
>>>> +       static constexpr size_t _S_rank = _Extents.size();
>>>> +
>>>> +       // For __r in [0, _S_rank], _S_dynamic_index[__r] is the number
>>>> +       // of dynamic extents up to (and not including) __r.
>>>> +       //
>>>> +       // If __r is the index of a dynamic extent, then
>>>> +       // _S_dynamic_index[__r] is the index of that extent in
>>>> +       // _M_dynamic_extents.
>>>> +       static constexpr auto _S_dynamic_index = [] consteval
>>>> +       {
>>>> +         array<size_t, _S_rank+1> __ret;
>>>> +         size_t __dyn = 0;
>>>> +         for(size_t __i = 0; __i < _S_rank; ++__i)
>>>> +           {
>>>> +             __ret[__i] = __dyn;
>>>> +             __dyn += _M_is_dyn(_Extents[__i]);
>>>> +           }
>>>> +         __ret[_S_rank] = __dyn;
>>>> +         return __ret;
>>>> +       }();
>>>> +
>>>> +       static constexpr size_t _S_rank_dynamic =
>>>> _S_dynamic_index[_S_rank];
>>>> +
>>>> +       // For __r in [0, _S_rank_dynamic), _S_dynamic_index_inv[__r]
>>>> is the
>>>> +       // index of the __r-th dynamic extent in _Extents.
>>>> +       static constexpr auto _S_dynamic_index_inv = [] consteval
>>>> +       {
>>>> +         array<size_t, _S_rank_dynamic> __ret;
>>>> +         for (size_t __i = 0, __r = 0; __i < _S_rank; ++__i)
>>>> +           if (_M_is_dyn(_Extents[__i]))
>>>> +             __ret[__r++] = __i;
>>>> +         return __ret;
>>>> +       }();
>>>> +
>>>> +       static constexpr size_t
>>>> +       _M_static_extent(size_t __r) noexcept
>>>> +       { return _Extents[__r]; }
>>>> +
>>>> +       constexpr _IndexType
>>>> +       _M_extent(size_t __r) const noexcept
>>>> +       {
>>>> +         auto __se = _Extents[__r];
>>>> +         if (__se == dynamic_extent)
>>>> +           return _M_dynamic_extents[_S_dynamic_index[__r]];
>>>> +         else
>>>> +           return __se;
>>>> +       }
>>>> +
>>>> +       template<size_t _OtherRank, typename _GetOtherExtent>
>>>> +         constexpr void
>>>> +         _M_init_dynamic_extents(_GetOtherExtent __get_extent) noexcept
>>>> +         {
>>>> +           for(size_t __i = 0; __i < _S_rank_dynamic; ++__i)
>>>> +             {
>>>> +               size_t __di = __i;
>>>> +               if constexpr (_OtherRank != _S_rank_dynamic)
>>>> +                 __di = _S_dynamic_index_inv[__i];
>>>> +               _M_dynamic_extents[__i] =
>>>> _M_int_cast(__get_extent(__di));
>>>> +             }
>>>> +         }
>>>> +
>>>> +       constexpr
>>>> +       _ExtentsStorage() noexcept = default;
>>>> +
>>>> +       template<typename _OIndexType, array _OExtents>
>>>> +         constexpr
>>>> +         _ExtentsStorage(const _ExtentsStorage<_OIndexType, _OExtents>&
>>>> +                         __other) noexcept
>>>> +         {
>>>> +           _M_init_dynamic_extents<_S_rank>([&__other](auto __i)
>>>>
>>> There is no need to be generic here, just use size_t as lambda
>>> parameter, instead of auto.
>>>
>>>> +             { return __other._M_extent(__i); });
>>>> +         }
>>>> +
>>>> +       template<typename _OIndexType, size_t _Nm>
>>>> +         constexpr
>>>> +         _ExtentsStorage(const span<_OIndexType, _Nm>& __exts) noexcept
>>>>
>>> I think I would adjust the signature to span<const _OIndexType> and then
> insert the constructor of span into
> the extents constructor. This will guarantee that we do not create
> unnecessary instantiations for mutable spans here.
> (I have recently made mistake of forgetting about else if constexpr code
> https://gcc.gnu.org/cgit/gcc/commit/?id=843b273c6851b71407b116584982b0389be4d6fd
> ,
> and double instantiations instead of reducing them, so looking into a ways
> to make this guaranteed).
>
> Good idea, I'd like to make sure I'm not missing a subtlety: In this case
> here it's
> about reducing the number of template instantiations, with the intent of
> reducing compile time.
>
Each such constructor will have a separate symbol (mangled name) and code
emitted,
so this is not only compile time, but also binary size.

> If there's more to it please expand, since in that case I'm
> very likely unaware of something relevant.
>

> +         {
>>>> +           _M_init_dynamic_extents<_Nm>(
>>>> +             [&__exts](auto __i) -> const _OIndexType&
>>>>
>>> Same as above.
>>>
>>>> +             { return __exts[__i]; });
>>>> +         }
>>>> +
>>>> +      private:
>>>> +       using _S_storage = __array_traits<_IndexType,
>>>> _S_rank_dynamic>::_Type;
>>>> +       [[no_unique_address]] _S_storage _M_dynamic_extents;
>>>> +      };
>>>> +
>>>> +    template<typename _OIndexType, typename _SIndexType>
>>>> +      concept __valid_index_type =
>>>> +       is_convertible_v<_OIndexType, _SIndexType> &&
>>>> +       is_nothrow_constructible_v<_SIndexType, _OIndexType>;
>>>> +
>>>> +    template<size_t _Extent, typename _IndexType>
>>>> +      concept
>>>> +      __valid_static_extent = _Extent == dynamic_extent
>>>> +       || _Extent <= numeric_limits<_IndexType>::max();
>>>> +  }
>>>> +
>>>> +  template<typename _IndexType, size_t... _Extents>
>>>> +    class extents
>>>> +    {
>>>> +      static_assert(is_integral_v<_IndexType>, "_IndexType must be
>>>> integral.");
>>>> +      static_assert(
>>>> +         (__mdspan::__valid_static_extent<_Extents, _IndexType> &&
>>>> ...),
>>>> +         "Extents must either be dynamic or representable as
>>>> _IndexType");
>>>> +
>>>> +    public:
>>>> +      using index_type = _IndexType;
>>>> +      using size_type = make_unsigned_t<index_type>;
>>>> +      using rank_type = size_t;
>>>> +
>>>> +      static constexpr rank_type
>>>> +      rank() noexcept { return _S_storage::_S_rank; }
>>>> +
>>>> +      static constexpr rank_type
>>>> +      rank_dynamic() noexcept { return _S_storage::_S_rank_dynamic; }
>>>> +
>>>> +      static constexpr size_t
>>>> +      static_extent(rank_type __r) noexcept
>>>> +      {
>>>> +       __glibcxx_assert(__r < rank());
>>>> +       if constexpr (rank() == 0)
>>>> +         __builtin_trap();
>>>> +
>>>> +       return _S_storage::_M_static_extent(__r);
>>>> +      }
>>>> +
>>>> +      constexpr index_type
>>>> +      extent(rank_type __r) const noexcept
>>>> +      {
>>>> +       __glibcxx_assert(__r < rank());
>>>> +       if constexpr (rank() == 0)
>>>> +         __builtin_trap();
>>>> +
>>>> +       return _M_dynamic_extents._M_extent(__r);
>>>> +      }
>>>> +
>>>> +      constexpr
>>>> +      extents() noexcept = default;
>>>> +
>>>> +    private:
>>>> +      static constexpr bool
>>>>
>>> I think this can be made consteval, we do not need symbols emitted to
>>> binary.
>>>
>>>> +      _M_is_less_dynamic(size_t __ext, size_t __oext)
>>>> +      { return (__ext != dynamic_extent) && (__oext ==
>>>> dynamic_extent); }
>>>> +
>>>> +      template<typename _OIndexType, size_t... _OExtents>
>>>> +       static constexpr bool
>>>>
>>> I think this can be made consteval, we do not need symbols emitted to
>>> binary.
>>>
>>>> +       _M_ctor_explicit()
>>>> +       {
>>>> +         return (_M_is_less_dynamic(_Extents, _OExtents) || ...)
>>>> +           || (numeric_limits<index_type>::max()
>>>> +               < numeric_limits<_OIndexType>::max());
>>>> +       }
>>>> +
>>>> +    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_dynamic_extents(__other._M_dynamic_extents)
>>>> +       { }
>>>> +
>>>> +      template<__mdspan::__valid_index_type<index_type>...
>>>> _OIndexTypes>
>>>> +       requires (sizeof...(_OIndexTypes) == rank()
>>>> +                 || sizeof...(_OIndexTypes) == rank_dynamic())
>>>> +       constexpr explicit extents(_OIndexTypes... __exts) noexcept
>>>> +       : _M_dynamic_extents(span<const _IndexType,
>>>> sizeof...(_OIndexTypes)>(
>>>> +           initializer_list{_S_storage::_M_int_cast(__exts)...}))
>>>> +       { }
>>>> +
>>>> +      template<__mdspan::__valid_index_type<index_type> _OIndexType,
>>>> size_t _Nm>
>>>> +       requires (_Nm == rank() || _Nm == rank_dynamic())
>>>>
>>> +       constexpr explicit(_Nm != rank_dynamic())
>>>> +       extents(span<_OIndexType, _Nm> __exts) noexcept
>>>>
>>> +       : _M_dynamic_extents(__exts)
>>>>
>>> I think we could pass  span<const _OIndexType, _Nm>(__ext) here, to
>> reduce the number of instantiations for constructor.
>>
>>> +       { }
>>>> +
>>>> +
>>>> +      template<__mdspan::__valid_index_type<index_type> _OIndexType,
>>>> size_t _Nm>
>>>> +       requires (_Nm == rank() || _Nm == rank_dynamic())
>>>> +       constexpr explicit(_Nm != rank_dynamic())
>>>> +       extents(const array<_OIndexType, _Nm>& __exts) noexcept
>>>> +       : _M_dynamic_extents(span<add_const_t<_OIndexType>,
>>>> _Nm>(__exts))
>>>>
>>> We can say const _OIndexType here.
>>>
>>>> +       { }
>>>> +
>>>> +      template<typename _OIndexType, size_t... _OExtents>
>>>> +       friend constexpr bool
>>>> +       operator==(const extents& __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;
>>>> +       }
>>>> +
>>>> +    private:
>>>> +      using _S_storage = __mdspan::_ExtentsStorage<
>>>> +       _IndexType, array<size_t, sizeof...(_Extents)>{_Extents...}>;
>>>> +      [[no_unique_address]] _S_storage _M_dynamic_extents;
>>>> +
>>>> +      template<typename _OIndexType, size_t... _OExtents>
>>>> +       friend class extents;
>>>> +    };
>>>> +
>>>> +  namespace __mdspan
>>>> +  {
>>>> +    template<typename _IndexType, size_t... _Counts>
>>>> +      auto __build_dextents_type(integer_sequence<size_t, _Counts...>)
>>>> +       -> extents<_IndexType, (_Counts, dynamic_extent)...>;
>>>> +
>>>> +    template<typename _Tp>
>>>> +      constexpr size_t
>>>>
>>> Make it consteval.
>>>
>>>> +      __dynamic_extent() { return dynamic_extent; }
>>>> +  }
>>>> +
>>>> +  template<typename _IndexType, size_t _Rank>
>>>> +    using dextents =
>>>> decltype(__mdspan::__build_dextents_type<_IndexType>(
>>>> +       make_index_sequence<_Rank>()));
>>>> +
>>>> +  template<typename... _Integrals>
>>>> +    requires (is_convertible_v<_Integrals, size_t> && ...)
>>>> +    explicit extents(_Integrals...) ->
>>>> +      extents<size_t, __mdspan::__dynamic_extent<_Integrals>()...>;
>>>>
>>>>  _GLIBCXX_END_NAMESPACE_VERSION
>>>>  }
>>>> 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