[RFC] Report missing move in mapping::operator().

Luc Grosheintz luc.grosheintz@gmail.com
Mon Jul 21 10:50:45 GMT 2025


Dear libstdc++ developers,

While writing the email to report the issue that mapping::operator()
doesn't use move, I noticed one other case in the C++26 padded layouts.
Therefore, I'd like to put the text up here first. Any feedback would be
highly welcome.

Kind regards,
Luc

--------------------------------------------------------------------------------

Numerous template classes in <mdspan> have template parameter IndexType.
While this template parameter is restricted to be a signed or unsigned
integer, these classes often accept user-defined classes that convert to
IndexType.

They're either passed as an array/span of OtherIndexType; or as a
template parameter pack. When passed as a template parameter pack, the
common pattern is

   template<class... OtherIndexTypes>
     requires std::is_convertible_v<OtherIndexTypes, IndexType> && ...
     void dummy(OtherIndexTypes... indices)
     {
       something(static_cast<IndexType>(std::move(indices))...);
     }

This pattern allows passing in objects that convert to IndexType only as
an rvalue reference, e.g.

   class RValueInt
   {
     constexpr
     operator int() && noexcept
     { return m_int; }

   private:
     int m_int;
   };

This pattern can be found:

   - a ctor of extents,
   - a ctor of mdspan,
   - in mdspan::operator[].

The five standardized layout mappings use a different pattern in their
operator(). Namely,

   static_cast<IndexType>(indices)...

This prevents the passing in objects of type RValueInt, because the
conversion isn't happening from an rvalue reference. This is addressed by
Items 1 - 5 in the Section Resolution.

A different pattern can be found a ctor for layout_{left,right}_padded.
Namely, directly passing an object of type OtherIndexType to
LEAST-MULTIPLE-AT-LEAST. This is addressed in Items 6 & 7 in the Section
Resolution.

Resolution:

1. [mdspan.layout.left.obs] Add 'std::move' such that Effects: reads as

   template<class... Indices>
   constexpr index_type operator()(Indices... i) const noexcept;
   [...]
   Effects: [...] Equivalent to:
       return ((static_cast<index_type>(std::move(i) * stride(P)) + ... + 0);

2. [mdspan.layout.right.obs] Analogous to 1.
3. [mdspan.layout.stride.obs] Analogous to 1.
4. [mdspan.layout.leftpad.obs] Analogous to 1; however i is called idxs.
5. [mdspan.layout.rightpad.obs] Analogous to 4.

6. [mdspan.layout.leftpad.cons] Rename the argument pad to padding and
introduce a variable `pad` for expositional purposes.

   template<class OtherIndexType>
     constexpr mapping(const extents_type& ext, OtherIndexType padding);

   <ins>Let pad = static_cast<index_type>(std::move(padding)).</ins>
   Constraints:
     — is_convertible_v<OtherIndexType, index_type> is true.
     — is_nothrow_constructible_v<index_type, OtherIndexType> is true.

   Preconditions:
     — pad<ins>ding</ins> is representable as a value of type index_type.
     — <del>extents_type::index-cast(pad)</del><ins>pad</ins> is greater than
     zero.
     — If rank_ is greater than one, then LEAST-MULTIPLE-AT-LEAST(pad,
     ext.extent(0)) is representable as a value of type index_type.
     — If rank_ is greater than one, then the product of
     LEAST-MULTIPLE-AT-LEAST(pad, ext.extent(0)) and all values ext.extent(k)
     with k in the range of [1, rank_) is representable as a value of type
     index_type.
     — If padding_value is not equal to dynamic_extent, padding_value equals
     <del>extents_type::index-cast(pad)</del><ins>pad</ins>.

   Effects: Direct-non-list-initializes extents_ with ext, and if rank_ is
   greater than one, direct-non-list-initializes stride-1 with
   LEAST-MULTIPLE-AT-LEAST(pad, ext.extent(0)).

7. [mdspan.layout.rightpad.cons] Analogous to 6, but not literally, due
variable names in the context differing slightly.


More information about the Libstdc++ mailing list