[PATCH v5 05/10] libstdc++: Implement layout_left from mdspan.
Luc Grosheintz
luc.grosheintz@gmail.com
Tue May 6 11:39:21 GMT 2025
On 5/6/25 11:28 AM, Tomasz Kaminski wrote:
> For better reference, here is illustration of the design I was thinking
> about:
> https://godbolt.org/z/7aTcM8fz4
> I would also consider having left_mapping_base to accept padding, where
> layout_left uses left_mapping_base<Extents::static_extent(1) !=
> dynamic_extent, Extents::static_extent(1), 1>.
>
Thank you for all the help! I think I'm doing what you're proposing. However,
now I'm seeing an issue related to `explicit(cond)`.
Essentially, it seems like with GCC the explicit for inherited ctors is ignored
while with Clang it isn't.
There's three variation of the issue: my working copy of layouts, a simplified
version I extracted from the working copy:
https://godbolt.org/z/8zfoeoc7j
Here extents are convertible if the IndexType is convertible.
and a modification of your reproducer:
https://godbolt.org/z/hG6YKosrf
Here extents are convertible if:
(Extent == dynamic_extent || Extents == OExtent) && ...
> On Tue, May 6, 2025 at 10:48 AM Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>> 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 <tkaminsk@redhat.com>
>> wrote:
>>
>>>
>>>
>>> On Mon, May 5, 2025 at 9:20 PM Luc Grosheintz <luc.grosheintz@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.grosheintz@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.grosheintz@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.grosheintz@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
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>
>>>>
>
More information about the Libstdc++
mailing list