The constructors that are inside mapping_left, that I think represents constructors with other extends: template<class U> mapping_left(const mapping_left_base<N, U>& other) : mapping_left_base<N, double>(other) {} Can be placed in mapping_left_base, and they will be inherited, as only copy/move constructors are shadowed.
On Tue, May 6, 2025 at 9:11 AM Tomasz Kaminski <tkami...@redhat.com> wrote: > > > On Mon, May 5, 2025 at 9:20 PM Luc Grosheintz <luc.groshei...@gmail.com> > wrote: > >> >> >> On 5/5/25 9:44 AM, Tomasz Kaminski wrote: >> > On Sat, May 3, 2025 at 2:39 PM Luc Grosheintz <luc.groshei...@gmail.com >> > >> > wrote: >> > >> >> >> >> >> >> On 4/30/25 7:13 AM, Tomasz Kaminski wrote: >> >>> Hi, >> >>> >> >>> As we will be landing patches for extends, this will become a separate >> >>> patch series. >> >>> I would prefer, if you could commit per layout, and start with >> >> layout_right >> >>> (default) >> >>> I try to provide prompt responses, so if that works better for you, >> you >> >> can >> >>> post a patch >> >>> only with this layout first, as most of the comments will apply to >> all of >> >>> them. >> >>> >> >>> For the general design we have constructors that allow conversion >> between >> >>> rank-0 >> >>> and rank-1 layouts left and right. This is done because they >> essentially >> >>> represents >> >>> the same layout. I think we could benefit from that in code by having >> a >> >>> base classes >> >>> for rank0 and rank1 mapping: >> >>> template<typename _Extents> >> >>> _Rank0_mapping_base >> >>> { >> >>> static_assert(_Extents::rank() == 0); >> >>> >> >>> template<OtherExtents> >> >>> // explicit, requires goes here >> >>> _Rank0_mapping_base(_Rank0_mapping_base<OtherExtents>); >> >>> >> >>> // All members layout_type goes her >> >>> }; >> >>> >> >>> template<typename _Extents> >> >>> _Rank1_mapping_base >> >>> { >> >>> static_assert(_Extents::rank() == 1); >> >>> // Static assert for product is much simpler here, as we need to >> >> check one >> >>> >> >>> template<OtherExtents> >> >>> // explicit, requires goes here >> >>> _Rank1_mapping_base(_Rank1_mapping_base<OtherExtents>); >> >>> >> >>> // Call operator can also be simplified >> >>> index_type operator()(index_type i) const // conversion happens at >> >> user >> >>> side >> >>> >> >>> // cosntructor from strided_layout of Rank1 goes here. >> >>> >> >>> // All members layout_type goes her >> >>> }; >> >>> Then we will specialize layout_left/right/stride to use >> >> _Rank0_mapping_base >> >>> as a base for rank() == 0 >> >>> and layout_left/right to use _Rank1_mapping as base for rank()1; >> >>> template<typename T, unsigned... Ids> >> >>> struct extents {}; >> >>> >> >>> struct layout >> >>> { >> >>> template<typename Extends> >> >>> struct mapping >> >>> { >> >>> // static assert that Extents mmyst be specialization of _Extents goes >> >> here. >> >>> } >> >>> }; >> >>> >> >>> template<typename _IndexType> >> >>> struct layout::mapping<extents<_IndexType>> >> >>> : _Rank0_mapping_base<extents<_IndexType>> >> >>> { >> >>> using layout_type = layout_left; >> >>> // Provides converting constructor. >> >>> using _Rank0_mapping_base<extents<_IndexType>>::_Rank0_mapping_base; >> >>> // This one is implicit; >> >>> mapping(_Rank0_mapping_base<extents<_IndexType>> const&); >> >>> }; >> >>> >> >>> template<typename _IndexType, unsigned _Ext> >> >>> struct layout::mapping<extents<_IndexType, _Ext>> >> >>> : _Rank1_mapping_base<extents<_IndexType>> >> >>> >> >>> { >> >>> using layout_type = layout_left; >> >>> // Provides converting constructor. >> >>> using _Rank0_mapping_base<extents<_IndexType>>::_Rank0_mapping_base; >> >>> // This one is implicit, allows construction from layout_right >> >>> mapping(_Rank1_mapping_base<extents<_IndexType>> const&); >> >>> }; >> >>> }; >> >>> >> >>> template<typename _IndexType, unsigned... _Ext> >> >>> requires sizeof..(_Ext) > = 2 >> >>> struct layout::mapping<extents<_IndexType, _Ext>> >> >>> >> >>> The last one is a generic implementation that you can use in yours. >> >>> Please also include a comment explaining that we are deviating from >> >>> standard text here. >> >>> >> >> >> >> Thank for reviewing and offering fast review cycles, I can't say I've >> >> ever felt that they were anything but wonderfully fast and I appologise >> >> for the delay (I've been away hiking for two days). >> >> >> >> The reason I implement all three is that I needed to see them all. >> >> Otherwise, I can see and "feel" the impact of the duplication (or >> >> efforts to reduce duplication). It's also to make sure I understand >> >> precisely how the layouts are similar and different. The idea is that >> >> you'd review one at a time; and by adding the others you can pick which >> >> one and have a glance at the other if it's helpful during review. >> >> >> >> The review contains three topics. This email responds to the idea of >> >> introducing a common base class. I believe I superficially understand >> >> the request. However, it's not clear to me what we gain. >> >> >> >> The reorganization seems to stress the how rank 0 and rank 1 layouts >> are >> >> similar; at the cost of making the uniformity of layout_left >> (regardless >> >> of rank) and layout_right (regardless of rank) less obvious. >> Personally, >> >> I quite like that we can express all layouts of one kind regardless of >> >> their rank, without resorting to special cases via specialization. >> >> >> >> To me the standard reads like the layouts are three separate, >> >> independent entities. However, because it would be too tedious to not >> >> allow conversion between layouts of rank 0 and 1, a couple of ctors >> were >> >> added. An example, of how the layouts are not consider related is that >> >> we can't compare layout_left and layout_right mappings for equality. >> >> >> >> If I count, the current implementation has 3 copies: layout_left, >> >> layout_right, layout_stride. The proposed changes add two (likely >> three) >> >> base classes which, require three specializations of layout_right and >> >> layout_left each. Therefore I end up with 6 copies of layout_left and >> >> layout_right; and 2 (or 3) base classes. Or if we manage it use the >> base >> >> classes for all layouts: 9 specialization, 2 (or 3) base classes. >> >> Therefore, in terms of numbers the restructuring doesn't seem >> favourable >> >> and I feel would require noticeably more repetition in the tests. >> >> >> > I think we can use a conditional base, to eliminate specializations, >> > and have layout_left_base. >> > >> >> >> >> While the rank zero and rank one versions are a lot simpler than the >> >> generic copy, they aren't entirely empty either (we'll likely need to >> >> use `using super::*` several times). Furthermore, I don't see any real >> >> simplifications in the generic copy. Meaning we still have a copy that >> >> has all the complexities and could (almost) handle the other two cases. >> >> >> > I think using a common bases, and allowing layout_left/layout_right to >> be >> > constructed from the base classes, will automatically enable the >> conversions >> > that are currently expressed via constructor: >> > layout_left <-> layout_right for ranks 0 and 1, >> > layout_stride <-> layout_left/right for rank 0 (because they are rank0) >> > >> >> >> >> Since layout_stride is a little different, I'm not sure we can reuse >> the >> >> base classes for it. For example it allows conversion more freely, but >> >> is only implicit for rank 0; whereas the other two allow implicit >> >> conversion between each other for rank 0 and 1. >> >> >> > This is why I said rank0_mapping_base should be used for layout_left, >> > layout_rigth and layout_stride >> > (and later layout_left_padeed, and layout_right_padded). >> > For rank1 this would be shared between layout_left, laout_right, >> > layout_left_paded and layout_right_paded. >> > >> >> >> >> Most importantly though, the reorganization makes it very hard to >> >> compare the implementation with the text of the standard. Therefore, >> >> making it more likely that a discrepancy goes unnoticed. >> >> >> >> One example of the subtleties involved in restructuring the code is the >> >> following: layout_right, layout_left support CTAD via the ctor >> >> `mapping(extents_type)`. However, when using specialization, that stops >> >> working and we'd need to add deduction guides to make the >> implementation >> >> conforming. I suspect a Godbolt can explain the situation better: >> >> https://godbolt.org/z/EdbE7ME6P >> > >> > We could use simple specializations, and derive from different base >> classes >> > using conditional_t. This would require having >> > >> > Thank you for looking deep into that idea, before we would go with totally > separate classes, > I would like to understands your points. > >> >> I gave this another try; and now I'm stumbling again as show here: >> https://godbolt.org/z/hfh9zE8ME > > This could be done as: > template<size_t N, typename T> > using mapping_middle_base = std::conditional_t<N == 0, rank0_base<T>, > rankN_middle_base<T>>; > > template<size_t N> > class mapping_middle : public mapping_middle_base<N, double> > { > using _Base = mapping_middle_base<N, double>; > public: > using _Base::_Base; > > mapping_middle(const _Base& other) > {} > }; > https://godbolt.org/z/M7jxq6qTK > >> >> >> As shown, it could be solved using three ctors `mapping(_Rank0_base)`, >> `mapping(_Rank1_base)` and `mapping(_RankN_base)` with the respective >> `requires` clauses. However that feels like it defeats the purpose. >> > I was thinking about having a simple constructor that constructs from > _Base. > >> >> Frankly, the savings in terms of lines of code are not great. Partly, >> because we often need `using extents_type = __super::extents`; or >> the ctor `mapping(mapping_base<OExtents>&)`. >> > I see that I was unclear there, my point was not about the code size in > terms of source file, > but the emitted code into binary. What I mean with separate layouts, for > E being every > static extent and and dynamic_extent, and every member function `fun` we > will have: > layout_left<extends<E>>::fun(); > layout_right<extends<E>>::fun(); > layout_left_padded<extends<E>>::fun(); > layout_right_padded<extends<E>>::fun(); > emitted in binary, and I would like to see if we can reduce it to > rank1_mapping_base<extends<E>>::fun(); > That's why I am looking into base class direction. > > >> The generic cases of `operator()`, `stride`, `required_span_size` tend >> to supported the rank 0 and rank 1 without special cases. (Making it >> harder to save lines of code by having special cases for rank 0, 1 and >> N.) >> > Again, I was not clear, when referring to code size, and more thinking of > binary size > and number of symbols. > >> >> The `stride` is a nice example of how it's all almost uniform, but not >> quite. It's present for layout_stride and the padded ones, but not >> layout_left and layout_right. For rank 1 it works for anything, just not >> layout_stride. (Further reducing the savings in terms of lines of code.) >> >> >> >> >> >> >> In summary: personally I feel using base classes adds repetition, >> >> complexity and makes it hard (for me impossible) to check that the >> >> implementation is conforming. >> >> >> >> Naturally, I can be convinced to change the implementation. Maybe you >> >> could explain a little what and why you don't like the admittedly very >> >> brute-force implementation. >> >> >> > There are a few things I had in mind. For rank 1 checking the >> precondition >> > for the size of multidimensional index space is trivial, >> > and we certainly could provide a __glibcxx_assert in such a case. This >> can >> > be done for the separate implementation, but I think >> > we will end up with repeated if constexpr checks in all mappings. This >> is >> > why in my mind, the reduced rank0 and rank1 cases >> > felt a bit special. >> > For example, mdspan<T, extents<N>> can be seen as replacement for >> span<N>, >> > which provides support for adjusting accessor >> > and pointer type. >> > >> > My original request, however, was however driven by code size. The >> > layout_left, layout_right, layout_left_padded, layout_right_padded >> > with rank 0, have members with same semantics (operator(), stride, ...), >> > and we could have only one definition for them, instead of four. >> > >> >> >> >>> >> >>> On Tue, Apr 29, 2025 at 2:56 PM Luc Grosheintz < >> luc.groshei...@gmail.com >> >>> >> >>> wrote: >> >>> >> >>>> Implements the parts of layout_left that don't depend on any of the >> >>>> other layouts. >> >>>> >> >>>> libstdc++/ChangeLog: >> >>>> >> >>>> * include/std/mdspan (layout_left): New class. >> >>>> >> >>>> Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com> >> >>>> --- >> >>>> libstdc++-v3/include/std/mdspan | 179 >> ++++++++++++++++++++++++++++++++ >> >>>> 1 file changed, 179 insertions(+) >> >>>> >> >>>> diff --git a/libstdc++-v3/include/std/mdspan >> >>>> b/libstdc++-v3/include/std/mdspan >> >>>> index 39ced1d6301..e05048a5b93 100644 >> >>>> --- a/libstdc++-v3/include/std/mdspan >> >>>> +++ b/libstdc++-v3/include/std/mdspan >> >>>> @@ -286,6 +286,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> >>>> >> >>>> namespace __mdspan >> >>>> { >> >>>> + template<typename _Extents> >> >>>> + constexpr typename _Extents::index_type >> >>>> + __fwd_prod(const _Extents& __exts, size_t __r) noexcept >> >>>> + { >> >>>> + typename _Extents::index_type __fwd = 1; >> >>>> + for(size_t __i = 0; __i < __r; ++__i) >> >>>> + __fwd *= __exts.extent(__i); >> >>>> + return __fwd; >> >>>> + } >> >>>> >> >>> As we are inside the standard library implementation, we can do some >> >> tricks >> >>> here, >> >>> and provide two functions: >> >>> // Returns the std::span(_ExtentsStorage::_Ext).substr(f, l); >> >>> // For extents forward to __static_exts >> >>> span<typename Extends::index_type> __static_exts(size_t f, size_t l); >> >>> // Returns the >> >>> >> >> >> std::span(_ExtentsStorage::_M_dynamic_extents).substr(_S_dynamic_index[f], >> >>> _S_dynamic_index[l); >> >>> span<typename Extends::index_type> __dynamic_exts(Extents const& c); >> >>> Then you can befriend this function both to extents and >> _ExtentsStorage. >> >>> Also add index_type members to _ExtentsStorage. >> >>> >> >>> Then instead of having fwd-prod and rev-prod I would have: >> >>> template<typename _Extents> >> >>> consteval size_t __static_ext_prod(size_t f, size_t l) >> >>> { >> >>> // multiply E != dynamic_ext from __static_exts >> >>> } >> >>> constexpr size __ext_prod(const _Extents& __exts, size_t f, size_t l) >> >>> { >> >>> // multiply __static_ext_prod<_Extents>(f, l) and each elements >> of >> >>> __dynamic_exts(__exts, f, l); >> >>> } >> >>> >> >>> Then fwd-prod(e, n) would be __ext_prod(e, 0, n), and rev_prod(e, n) >> >> would >> >>> be __ext_prod(e, __ext.rank() -n, n, __ext.rank()) >> >>> >> >>> >> >>>> + >> >>>> + template<typename _Extents> >> >>>> + constexpr typename _Extents::index_type >> >>>> + __rev_prod(const _Extents& __exts, size_t __r) noexcept >> >>>> + { >> >>>> + typename _Extents::index_type __rev = 1; >> >>>> + for(size_t __i = __r + 1; __i < __exts.rank(); ++__i) >> >>>> + __rev *= __exts.extent(__i); >> >>>> + return __rev; >> >>>> + } >> >>>> + >> >>>> template<typename _IndexType, size_t... _Counts> >> >>>> auto __build_dextents_type(integer_sequence<size_t, >> _Counts...>) >> >>>> -> extents<_IndexType, ((void) _Counts, >> dynamic_extent)...>; >> >>>> @@ -304,6 +324,165 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> >>>> explicit extents(_Integrals...) -> >> >>>> extents<size_t, >> __mdspan::__dynamic_extent<_Integrals>()...>; >> >>>> >> >>>> + struct layout_left >> >>>> + { >> >>>> + template<typename _Extents> >> >>>> + class mapping; >> >>>> + }; >> >>>> + >> >>>> + namespace __mdspan >> >>>> + { >> >>>> + template<typename _Tp> >> >>>> + constexpr bool __is_extents = false; >> >>>> + >> >>>> + template<typename _IndexType, size_t... _Extents> >> >>>> + constexpr bool __is_extents<extents<_IndexType, _Extents...>> >> = >> >>>> true; >> >>>> + >> >>>> + template<size_t _Count> >> >>>> + struct _LinearIndexLeft >> >>>> + { >> >>>> + template<typename _Extents, typename... _Indices> >> >>>> + static constexpr typename _Extents::index_type >> >>>> + _S_value(const _Extents& __exts, typename >> _Extents::index_type >> >>>> __idx, >> >>>> + _Indices... __indices) noexcept >> >>>> + { >> >>>> + return __idx + __exts.extent(_Count) >> >>>> + * _LinearIndexLeft<_Count + 1>::_S_value(__exts, >> >> __indices...); >> >>>> + } >> >>>> + >> >>>> + template<typename _Extents> >> >>>> + static constexpr typename _Extents::index_type >> >>>> + _S_value(const _Extents&) noexcept >> >>>> + { return 0; } >> >>>> + }; >> >>>> + >> >>>> + template<typename _Extents, typename... _Indices> >> >>>> + constexpr typename _Extents::index_type >> >>>> + __linear_index_left(const _Extents& __exts, _Indices... >> >> __indices) >> >>>> + { >> >>>> + return _LinearIndexLeft<0>::_S_value(__exts, __indices...); >> >>>> + } >> >>>> >> >>> This can be eliminated by fold expressions, see below. >> >>> >> >>>> + >> >>>> + template<typename _IndexType, typename _Tp, size_t _Nm> >> >>>> + consteval bool >> >>>> + __is_representable_product(array<_Tp, _Nm> __factors) >> >>>> + { >> >>>> + size_t __rest = numeric_limits<_IndexType>::max(); >> >>>> + for(size_t __i = 0; __i < _Nm; ++__i) >> >>>> + { >> >>>> + if (__factors[__i] == 0) >> >>>> + return true; >> >>>> + __rest /= _IndexType(__factors[__i]); >> >>>> + } >> >>>> + return __rest > 0; >> >>>> + } >> >>>> >> >>> I would replace that with >> >>> template<IndexType> >> >>> consteval size_t __div_reminder(span<const size_t, _Nm> __factors, >> size_t >> >>> __val) >> >>> { >> >>> size_t __rest = val; >> >>> for(size_t __i = 0; __i < _Nm; ++__i) >> >>> { >> >>> if (__factors[__i] == dynamic_extent) >> >>> continue; >> >>> if (__factors[__i] != 0) >> >>> return val; >> >>> __rest /= _IndexType(__factors[__i]); >> >>> if (__res == 0) >> >>> return 0; >> >>> } >> >>> return __rest; >> >>> } >> >>> >> >>> We can express the is presentable check as >> >>> static constexpr __dyn_reminder = >> >> __div_reminder(__static_exts<Extents>(0, >> >>> rank()), std::numeric_limits<Index>::max()); >> >>> static_assert(__dyn_reminder > 0); >> >>> However, with __dyn_reminder value, the precondition >> >>> https://eel.is/c++draft/mdspan.layout#left.cons-1, >> >>> can be checked by doing equivalent of __div_remainder for >> __dyn_extents >> >>> with __val being __dyn_reminder. >> >>> >> >>> >> >>>> + >> >>>> + template<typename _Extents> >> >>>> + consteval array<typename _Extents::index_type, >> _Extents::rank()> >> >>>> + __static_extents_array() >> >>>> + { >> >>>> + array<typename _Extents::index_type, _Extents::rank()> >> __exts; >> >>>> + for(size_t __i = 0; __i < _Extents::rank(); ++__i) >> >>>> + __exts[__i] = _Extents::static_extent(__i); >> >>>> + return __exts; >> >>>> + } >> >>>> >> >>> >> >>> Replaced by __static_exts accessor, as described above. >> >>> >> >>> >> >>>> + >> >>>> + template<typename _Extents, typename _IndexType> >> >>>> + concept __representable_size = _Extents::rank_dynamic() != 0 >> >>>> + || __is_representable_product<_IndexType>( >> >>>> + __static_extents_array<_Extents>()); >> >>>> + >> >>>> + template<typename _Extents> >> >>>> + concept __layout_extent = __representable_size< >> >>>> + _Extents, typename _Extents::index_type>; >> >>>> + } >> >>>> >> >>> + >> >>>> + template<typename _Extents> >> >>>> + class layout_left::mapping >> >>>> + { >> >>>> + static_assert(__mdspan::__layout_extent<_Extents>, >> >>>> + "The size of extents_type is not representable as >> index_type."); >> >>>> + 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 = layout_left; >> >>>> + >> >>>> + constexpr >> >>>> + mapping() noexcept = default; >> >>>> + >> >>>> + constexpr >> >>>> + mapping(const mapping&) noexcept = default; >> >>>> + >> >>>> + constexpr >> >>>> + mapping(const extents_type& __extents) noexcept >> >>>> + : _M_extents(__extents) >> >>>> + { >> >>>> >> >>> >> >>> >> >>> >> >>>> } >> >>>> + >> >>>> + template<typename _OExtents> >> >>>> + requires (is_constructible_v<extents_type, _OExtents>) >> >>>> + constexpr explicit(!is_convertible_v<_OExtents, >> extents_type>) >> >>>> + mapping(const mapping<_OExtents>& __other) noexcept >> >>>> + : _M_extents(__other.extents()) >> >>>> + { >> >>> >> >>> Here we could do checks at compile time: >> >>> if constexpr(_OExtents::rank_dynamic() == 0) >> >>> static_assert( __div_remainder(...) > 0); >> >>> } >> >>> >> >>> >> >>>> } >> >>>> + >> >>>> + constexpr mapping& >> >>>> + operator=(const mapping&) noexcept = default; >> >>>> + >> >>>> + constexpr const extents_type& >> >>>> + extents() const noexcept { return _M_extents; } >> >>>> + >> >>>> + constexpr index_type >> >>>> + required_span_size() const noexcept >> >>>> + { return __mdspan::__fwd_prod(_M_extents, _M_extents.rank()); >> } >> >>>> + >> >>>> + template<__mdspan::__valid_index_type<index_type>... _Indices> >> >>>> >> >>> // Because we extracted rank0 and rank1 specializations >> >>> >> >>>> + requires (sizeof...(_Indices) + 1 == extents_type::rank()) >> >>>> + constexpr index_type >> >>>> + operator()(index_type __idx, _Indices... __indices) const >> >> noexcept >> >>>> + { >> >>>> >> >>> This could be implemented as, please synchronize the names. >> >>> if constexpr (!is_same_v<_Indices, index_type> || ...) >> >>> // Reduce the number of instantations. >> >>> return operator()(index_type _idx0, >> >>> static_cast<index_type>(std::move(__indices))....); >> >>> else >> >>> { >> >>> // This can be used for layout stride, if you start with >> __res = >> >> 0; >> >>> index_type __res = _idx0; >> >>> index_type __mult = _M_extents.extent(0); >> >>> auto __update = [&__res, &__mult, __pos = 1u](index_type >> __idx) >> >>> mutable >> >>> { >> >>> __res += __idx * __mult; >> >>> __mult *= _M_extents.extent(__pos); >> >>> ++__pos; >> >>> }; >> >>> // Fold over invocation of lambda >> >>> (__update(_Indices), ....); >> >>> return __res; >> >>> } >> >>> >> >>> This could be even simpler and written as (use for layout stride): >> >>> size_t __pos = 0; >> >>> return (index_type(0) + ... + __indices * stride(__pos++)); >> >>> Here, I prefer to avoid multiplying multiple times. >> >>> >> >>> >> >>> + return __mdspan::__linear_index_left( >> >>>> + _M_extents, static_cast<index_type>(__indices)...); >> >>>> + } >> >>>> + >> >>>> + static constexpr bool >> >>>> + is_always_unique() noexcept { return true; } >> >>>> + >> >>>> + static constexpr bool >> >>>> + is_always_exhaustive() noexcept { return true; } >> >>>> + >> >>>> + static constexpr bool >> >>>> + is_always_strided() noexcept { return true; } >> >>>> + >> >>>> + static constexpr bool >> >>>> + is_unique() noexcept { return true; } >> >>>> + >> >>>> + static constexpr bool >> >>>> + is_exhaustive() noexcept { return true; } >> >>>> + >> >>>> + static constexpr bool >> >>>> + is_strided() noexcept { return true; } >> >>>> + >> >>>> + constexpr index_type >> >>>> + stride(rank_type __i) const noexcept >> >>>> + requires (extents_type::rank() > 0) >> >>>> + { >> >>>> + __glibcxx_assert(__i < extents_type::rank()); >> >>>> + return __mdspan::__fwd_prod(_M_extents, __i); >> >>>> + } >> >>>> + >> >>>> + template<typename _OExtents> >> >>>> + requires (extents_type::rank() == _OExtents::rank()) >> >>>> + friend constexpr bool >> >>>> + operator==(const mapping& __self, const mapping<_OExtents>& >> >>>> __other) >> >>>> + noexcept >> >>>> + { return __self.extents() == __other.extents(); } >> >>>> + >> >>>> + private: >> >>>> + [[no_unique_address]] extents_type _M_extents; >> >>>> + }; >> >>>> + >> >>>> _GLIBCXX_END_NAMESPACE_VERSION >> >>>> } >> >>>> #endif >> >>>> -- >> >>>> 2.49.0 >> >>>> >> >>>> >> >>> >> >> >> >> >> > >> >>