This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4314. Missing move in mdspan layout mapping::operator()

Section: 23.7.3.4 [mdspan.layout] Status: New Submitter: Luc Grosheintz Opened: 2025-08-13 Last modified: 2025-08-16

Priority: Not Prioritized

View other active issues in [mdspan.layout].

View all other issues in [mdspan.layout].

View all issues with New status.

Discussion:

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

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 Proposed 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 and 7 in the Proposed Resolution.

This inconsistency was noticed while fixing PR121061 and these changes have been applied to all layout mappings implemented in libstdc++.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.7.3.4.5.3 [mdspan.layout.left.obs] as indicated:

    template<class... Indices>
      constexpr index_type operator()(Indices... i) const noexcept;
    

    -2- Constraints: […]

    -3- Preconditions: […]

    -4- Effects: Let P be a parameter pack such that

    is_same_v<index_sequence_for<Indices...>, index_sequence<P...>>
    

    is true. Equivalent to:

    return ((static_cast<index_type>(std::move(i)) * stride(P)) + ... + 0);
    
  2. Modify 23.7.3.4.6.3 [mdspan.layout.right.obs] as indicated:

    template<class... Indices>
      constexpr index_type operator()(Indices... i) const noexcept;
    

    -2- Constraints: […]

    -3- Preconditions: […]

    -4- Effects: Let P be a parameter pack such that

    is_same_v<index_sequence_for<Indices...>, index_sequence<P...>>
    

    is true. Equivalent to:

    return ((static_cast<index_type>(std::move(i)) * stride(P)) + ... + 0);
    
  3. Modify 23.7.3.4.7.4 [mdspan.layout.stride.obs] as indicated:

    template<class... Indices>
      constexpr index_type operator()(Indices... i) const noexcept;
    

    -2- Constraints: […]

    -3- Preconditions: […]

    -4- Effects: Let P be a parameter pack such that

    is_same_v<index_sequence_for<Indices...>, index_sequence<P...>>
    

    is true. Equivalent to:

    return ((static_cast<index_type>(std::move(i)) * stride(P)) + ... + 0);
    
  4. Modify 23.7.3.4.8.4 [mdspan.layout.leftpad.obs] as indicated:

    template<class... Indices>
      constexpr index_type operator()(Indices... idxs) const noexcept;
    

    -3- Constraints: […]

    -4- Preconditions: […]

    -5- Returns: ((static_cast<index_type>(std::move(idxs)) * stride(P_rank)) + ... + 0).

  5. Modify 23.7.3.4.9.4 [mdspan.layout.rightpad.obs] as indicated:

    template<class... Indices>
      constexpr index_type operator()(Indices... idxs) const noexcept;
    

    -3- Constraints: […]

    -4- Preconditions: […]

    -5- Returns: ((static_cast<index_type>(std::move(idxs)) * stride(P_rank)) + ... + 0).

  6. Modify 23.7.3.4.8.3 [mdspan.layout.leftpad.cons] as indicated:

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

    Let pad = static_cast<index_type>(std::move(padding)).

    -3- Constraints: […]

    -4- Preconditions:

    1. (4.1) — padding is representable as a value of type index_type.

    2. (4.2) — extents_type::index-cast(pad)pad is greater than zero.

    3. (4.3) — If rank_ is greater than one, then LEAST-MULTIPLE-AT-LEAST(pad, ext.extent(0)) is representable as a value of type index_type.

    4. (4.4) — 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.

    5. (4.5) — If padding_value is not equal to dynamic_extent, padding_value equals extents_type::index-cast(pad)pad.

    -5- Effects: […]

  7. Modify 23.7.3.4.9.3 [mdspan.layout.rightpad.cons] as indicated:

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

    Let pad = static_cast<index_type>(std::move(padding)).

    -3- Constraints: […]

    -4- Preconditions:

    1. (4.1) — padding is representable as a value of type index_type.

    2. (4.2) — extents_type::index-cast(pad)pad is greater than zero.

    3. (4.3) — If rank_ is greater than one, then LEAST-MULTIPLE-AT-LEAST(pad, ext.extent(rank_ - 1)) is representable as a value of type index_type.

    4. (4.4) — If rank_ is greater than one, then the product of LEAST-MULTIPLE-AT-LEAST(pad, ext.extent(rank_ - 1)) and all values ext.extent(k) with k in the range of [1, rank_ - 1) is representable as a value of type index_type.

    5. (4.5) — If padding_value is not equal to dynamic_extent, padding_value equals extents_type::index-cast(pad)pad.

    -5- Effects: […]