cpp-proposals-pub icon indicating copy to clipboard operation
cpp-proposals-pub copied to clipboard

Issaquah (Feb 2023) P2630R2 presentation

Open mhoemmen opened this issue 2 years ago • 2 comments

P2630R2: submdspan

submdspan: multidimensional array slicing

  • Split from P0009 after LEWG review

  • P2630 expands facility to make it work for custom layouts, and adds strided_slice

R2 changes

  • Added discussion choice of customization point mechanism

  • Renamed strided_index_range to strided_slice

  • Introduced named struct combining mapping and offset as return type for submdspan_mapping

  • Removed redundant constraints, which are covered by mandates; this improves error messages for users

Customization point mechanism

  • Paper: Section 2.1.3 "Pure ADL vs. CPO vs. tag invoke"

  • Applies P2279's criteria to judge value of different mechanisms

  • Most users will never call submdspan_mapping directly

  • submdspan_mapping only needs to be implemented for custom layout mappings, a rare use case

  • submdspan_mapping has no default implementation (unlike, say, std::swap)

  • Name submdspan_mapping is extremely specific, making opt-in essentially explicit

  • Incorrect opt-in is easy to diagnose: return type must be a layout mapping with specific extents

  • This and other discussion in paper suggest no significant benefits in preferring CPOs or tag_invoke over pure ADL for submdspan_mapping. However, we would accept LEWG's preference for tag_invoke here.

mhoemmen avatar Feb 06 '23 20:02 mhoemmen

Submdspan Recap

submdspan lets you take subsets of a an mdspan

An example:

// Some data representing 2x3 matrices
double data[6] = { 11, 12, 13,
                   21, 22, 23 };
// An rank-2 mdspan of the data
mdspan matrix(data, 2, 3);


mdspan<double,extents<size_t, dynamic_extent>, layout_stride>
   col_0 = submdspan(matrix, full_extent, 0);
// col_0 == 11, 21
// col_0.stride(0) == 3

mdspan<double,extents<size_t, dynamic_extent>, layout_right>
   row_1 = submdspan(matrix, 1, full_extent);
// row_1 == 21, 22, 23
// row_1.stride(0) == 1

// in practice this is a place where auto is your friend:
auto row_0 = submdspan(matrix, 0, full_extent);

submdspan in our proposal is effectively implemented as:

// submdspan takes and mdspan and slice specifiers
template<class T, class E, class L, class A, class ... SliceArgs)
auto submdspan(const mdspan<T,E,L,A>& src, SliceArgs ... args) {
  // get the new accessor from the old one
  typename A::offset_policy sub_acc(src.accessor());

  // get new mapping as well as linear offset from submdspan_mapping
  // This function is a customization point and will be called using ADL!
  // returns an aggregate type "submdspan_mapping_result" 
  auto sub_map = submdspan_mapping(src.mapping(), args...);

  // Check that we didn't get an invalid submdspan_mapping impl
  #ifndef NDEBUG
  // submdspan_extents is NOT a customization point.
  auto expected_extents = submdspan_extents(src.extents(), args...); 
  // check that the types match
  static_assert(is_same_v<decltype(sub_map.mapping)::extent_type, 
                        decltype(expected_extents)>);
  // check that the values match
  assert(expected_extents==sub_map.mapping.extents());
  #endif

  // Compute the new data handle
  auto sub_handle = src.accessor().offset(src.data_handle(), sub_map.offset);

  // return the new mdspan
  return mdspan(sub_handle, sub_map.mapping, sub_acc);
}

Incorporated feedback from last review:

Return Type of submdspan_mapping

Changed from pair to a specific named aggregate type.

Before:

template<class Extents, class... SliceSpecifiers>
    constexpr auto submdspan_mapping(
      const layout_left::mapping<Extents>& src, 
      SliceSpecifiers ... slices) {
   ...
   return pair{new_mapping, offset};
}

Now:

template<class LayoutMapping>
struct submdspan_mapping_result {
  LayoutMapping mapping;
  size_t offset;
};

template<class Extents, class... SliceSpecifiers>
    constexpr auto submdspan_mapping(
      const layout_left::mapping<Extents>& src, 
      SliceSpecifiers ... slices) {
   ...
   return submdspan_mapping_result{new_mapping, offset};
}

Explore Customization Point Design

General Considerations

  • Most users will never call submdspan_mapping directly - mostly folks will call submdspan
  • submdspan_mapping only needs to be implemented for custom layout mappings, a rare use case
  • submdspan_mapping has no default implementation (unlike, say, std::swap)

Explicit Opt-In

  • Problem: recognizing that a function is an implementation of a customization point (how do you know that my swap friend member is meant to be used with std::swap?
  • submdspan_mapping is a very non-general name - in particular in its overload which takes a mapping and slice specifiers.

Diagnose Incorrect Opt-In

  • We know a lot about the return value of submdspan_mapping, all of which can be checked.
    • must return a layout mapping
    • the return types extents_type is mandated
    • the return values extents() values are mandated.

Associated Types

  • for some customization points things like the return type are well defined by a single template parameter (e.g. return type of the begin function is generally T::iterator_type etc.
  • for submdspan_mapping none of the arguments know anything about the return type. The return type is really a combinatorical property of all the arguments.
    • For example in the following example the input is a layout_left mapping but the output is a layout_stride mapping
auto sub = submdspan_mapping(layout_left::mapping(4,4), 0, full_extent);
  • But reorder the last two arguments and the return type is a layout_left mapping

We concluded: Pure ADL is good enough for us.

crtrott avatar Feb 07 '23 22:02 crtrott

GodBolt example of custom layout/accessor in combo with submdspan: https://godbolt.org/z/EhMrYvTfo

crtrott avatar Feb 08 '23 02:02 crtrott