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