From e2a6083b9a245f396f33cd75a3cb823203e28418 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:40:55 +0000 Subject: [PATCH 1/8] Initial plan From ad567e75e4170249d510b9b4e3c864102e37ff43 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:56:17 +0000 Subject: [PATCH 2/8] Add submdspan_canonicalize_slices function with design improvements Co-authored-by: crtrott <9490481+crtrott@users.noreply.github.com> --- .../experimental/__p2630_bits/submdspan.hpp | 1 + .../submdspan_canonicalize_slices.hpp | 487 ++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test_canonicalize_slices.cpp | 332 ++++++++++++ 4 files changed, 821 insertions(+) create mode 100644 include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp create mode 100644 tests/test_canonicalize_slices.cpp diff --git a/include/experimental/__p2630_bits/submdspan.hpp b/include/experimental/__p2630_bits/submdspan.hpp index abddd0b5..f2f18e17 100644 --- a/include/experimental/__p2630_bits/submdspan.hpp +++ b/include/experimental/__p2630_bits/submdspan.hpp @@ -17,6 +17,7 @@ #pragma once #include "submdspan_extents.hpp" +#include "submdspan_canonicalize_slices.hpp" #include "submdspan_mapping.hpp" namespace MDSPAN_IMPL_STANDARD_NAMESPACE { diff --git a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp new file mode 100644 index 00000000..f524bcf2 --- /dev/null +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -0,0 +1,487 @@ +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#pragma once + +#include "submdspan_extents.hpp" +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#if MDSPAN_HAS_CXX_17 + +namespace detail { + +// ============================================================ +// de_ice: extract the value of an integral-constant-like type +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class T, + /* requires */ (std::is_integral_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr T de_ice(T val) { + return val; +} + +MDSPAN_TEMPLATE_REQUIRES( + class T, + /* requires */ (is_integral_constant_like_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr decltype(T::value) de_ice([[maybe_unused]] T) { + return T::value; +} + +// ============================================================ +// index_cast: cast to IndexType, preserving integral-constant nature +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + class OtherIndexType, + /* requires */ ( + std::is_signed_v> || + std::is_unsigned_v> + ) +) +MDSPAN_INLINE_FUNCTION +constexpr auto index_cast(OtherIndexType&& i) noexcept { + return i; +} + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + class OtherIndexType, + /* requires */ ( + ! std::is_signed_v> && + ! std::is_unsigned_v> + ) +) +MDSPAN_INLINE_FUNCTION +constexpr auto index_cast(OtherIndexType&& i) noexcept { + return static_cast(i); +} + +// ============================================================ +// canonical_ice: canonicalize a value to IndexType, +// preserving integral-constant nature when possible +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + class S, + /* requires */ (std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr auto canonical_ice([[maybe_unused]] S s) { + static_assert(std::is_signed_v || std::is_unsigned_v); + if constexpr (is_integral_constant_like_v) { + return cw(index_cast(S::value))>; + } + else { + return static_cast(index_cast(s)); + } +} + +// ============================================================ +// subtract_ice: subtract two values, preserving integral-constant +// nature when both inputs are integral-constant-like +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr auto subtract_ice([[maybe_unused]] X x, [[maybe_unused]] Y y) { + if constexpr ( + is_integral_constant_like_v> && + is_integral_constant_like_v>) + { + return cw(Y::value) - canonical_ice(X::value))>; + } + else { + return canonical_ice(y) - canonical_ice(x); + } +} + +// ============================================================ +// check_static_bounds_result: result of a compile-time bounds check +// ============================================================ + +enum class check_static_bounds_result { + in_bounds, + out_of_bounds, + unknown +}; + +// ============================================================ +// is_std_complex: detect std::complex (for pre-tuple-like compilers) +// ============================================================ + +// Clang 21.0.0 does not define __cpp_lib_tuple_like, so it does not +// support the tuple protocol for std::complex. Interestingly, it permits +// structured binding, but decomposes it into one element, not two. +// We work around with a special canonicalization case. +#if ! defined(__cpp_lib_tuple_like) || (__cpp_lib_tuple_like < 202311L) +template +constexpr bool is_std_complex = false; +template +constexpr bool is_std_complex> = true; +#endif + +// ============================================================ +// check_static_bounds: compile-time bounds check for a slice +// +// Returns whether the k-th slice is statically in bounds, +// out of bounds, or unknown (dynamic bounds or dynamic slice values). +// +// This function is called only in static_assert contexts. +// ============================================================ + +template +constexpr check_static_bounds_result check_static_bounds( + const extents&) +{ +#if defined(__cpp_pack_indexing) + constexpr size_t Exts_k = Exts...[k]; +#else + constexpr size_t Exts_k = [] () { + size_t result = 0; + size_t i = 0; + (void) ((i++ == k ? (result = Exts, true) : false) || ...); + return result; + } (); +#endif + + if constexpr (std::is_convertible_v) { + return check_static_bounds_result::in_bounds; + } + else if constexpr (std::is_convertible_v) { + if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(S_k{}) < 0) { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k <= static_cast(de_ice(S_k{}))) + { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + static_cast(de_ice(S_k{})) < Exts_k) + { + return check_static_bounds_result::in_bounds; + } + else { + return check_static_bounds_result::unknown; + } + } + else { + return check_static_bounds_result::unknown; + } + } + else if constexpr (is_strided_slice::value) { + using offset_type = typename S_k::offset_type; + + if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(offset_type{}) < 0) { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < static_cast(de_ice(offset_type{}))) + { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr (is_integral_constant_like_v) { + using extent_type = typename S_k::extent_type; + + if constexpr (de_ice(offset_type{}) + de_ice(extent_type{}) < 0) { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < + static_cast(de_ice(offset_type{}) + de_ice(extent_type{}))) + { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + 0 <= de_ice(offset_type{}) && + de_ice(offset_type{}) <= + de_ice(offset_type{}) + de_ice(extent_type{}) && + static_cast( + de_ice(offset_type{}) + de_ice(extent_type{})) <= Exts_k) + { + return check_static_bounds_result::in_bounds; + } + else { + return check_static_bounds_result::unknown; + } + } + else { + return check_static_bounds_result::unknown; + } + } + else { + return check_static_bounds_result::unknown; + } + } +#if ! defined(__cpp_lib_tuple_like) || (__cpp_lib_tuple_like < 202311L) + else if constexpr (is_std_complex) { + return check_static_bounds_result::unknown; + } +#endif + else { + // General pair-like case: attempt to get the first and second elements. + // If S_k cannot be structured-bound into two elements, this is ill-formed, + // which implements the Mandates clause. + auto get_first = [] (S_k s_k) { + auto [s_k0, _x] = s_k; + return s_k0; + }; + auto get_second = [] (S_k s_k) { + auto [_x, s_k1] = s_k; + return s_k1; + }; + using S_k0 = decltype(get_first(std::declval())); + using S_k1 = decltype(get_second(std::declval())); + if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(S_k0{}) < 0) { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < static_cast(de_ice(S_k0{}))) + { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(S_k1{}) < de_ice(S_k0{})) { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < static_cast(de_ice(S_k1{}))) + { + return check_static_bounds_result::out_of_bounds; + } + else if constexpr ( + Exts_k != dynamic_extent && + 0 <= de_ice(S_k0{}) && + de_ice(S_k0{}) <= de_ice(S_k1{}) && + static_cast(de_ice(S_k1{})) <= Exts_k) + { + return check_static_bounds_result::in_bounds; + } + else { + return check_static_bounds_result::unknown; + } + } + else { + return check_static_bounds_result::unknown; + } + } + else { + return check_static_bounds_result::unknown; + } + } +} + +// ============================================================ +// get_kth_in_pack: get the k-th element of a parameter pack +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr decltype(auto) get_kth_in_pack(First&& first, Rest&&... rest) { + static_assert(k <= sizeof...(Rest)); + if constexpr (k == 0) { + return std::forward(first); + } + else { + return get_kth_in_pack(std::forward(rest)...); + } +} + +// ============================================================ +// check_submdspan_slice_mandate: mandate check for the k-th slice +// +// Contains only static_asserts; no actual computation. +// Separated from submdspan_canonicalize_one_slice so that +// mandate checking and canonicalization are distinct concerns. +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr void check_submdspan_slice_mandate( + const extents&, + [[maybe_unused]] Slice) +{ + static_assert( + check_static_bounds(extents{}) != + check_static_bounds_result::out_of_bounds); +} + +// ============================================================ +// check_submdspan_slice_mandates: mandate check for all slices +// +// Calls check_submdspan_slice_mandate for each slice. +// Separated from canonicalization so mandate checking is explicit. +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr void check_submdspan_slice_mandates( + std::index_sequence, + const extents& exts, + Slices... slices) +{ + (check_submdspan_slice_mandate(exts, get_kth_in_pack(slices...)), ...); +} + +// ============================================================ +// submdspan_canonicalize_one_slice: canonicalize the k-th slice +// +// This function performs ONLY the conversion to canonical form. +// Mandate checking (static_asserts) is NOT done here; it is +// done separately by check_submdspan_slice_mandates. +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr auto submdspan_canonicalize_one_slice( + [[maybe_unused]] const extents& exts, + [[maybe_unused]] Slice s) +{ + if constexpr (std::is_convertible_v) { + return full_extent; // canonical full-extent slice + } + else if constexpr (std::is_convertible_v) { + return canonical_ice(s); // canonical integer index + } + else if constexpr (is_strided_slice::value) { + // Canonicalize each component of the strided_slice + auto offset = canonical_ice(s.offset); + auto extent = canonical_ice(s.extent); + auto stride = canonical_ice(s.stride); + return strided_slice{ + /* .offset = */ offset, + /* .extent = */ extent, + /* .stride = */ stride + }; + } +#if ! defined(__cpp_lib_tuple_like) || (__cpp_lib_tuple_like < 202311L) + else if constexpr (is_std_complex) { + // std::complex used as [real, imag) range (offset, offset+extent) + auto offset = canonical_ice(s.real()); + auto extent = canonical_ice(s.imag() - s.real()); + auto stride = cw; + return strided_slice{ + /* .offset = */ offset, + /* .extent = */ extent, + /* .stride = */ stride + }; + } +#endif + else { + // General pair-like case: structured binding into [first, last) + auto [s_k0, s_k1] = s; + using S_k0 = decltype(s_k0); + using S_k1 = decltype(s_k1); + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + + auto offset = canonical_ice(s_k0); + auto extent = subtract_ice(s_k0, s_k1); + auto stride = cw; + return strided_slice{ + /* .offset = */ offset, + /* .extent = */ extent, + /* .stride = */ stride + }; + } +} + +// ============================================================ +// submdspan_canonicalize_slices_impl: implementation helper +// +// First performs mandate checks (static_asserts), then +// returns a detail::tuple of canonical slices. +// Using detail::tuple instead of std::tuple ensures device +// code compatibility (e.g., CUDA). +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + size_t... Inds, + class IndexType, + size_t... Extents, + class... Slices, + /* requires */ (sizeof...(Slices) == sizeof...(Extents)) +) +MDSPAN_INLINE_FUNCTION +constexpr auto submdspan_canonicalize_slices_impl( + std::index_sequence, + const extents& exts, + Slices... slices) +{ + // Mandate checks (static_asserts only, no computation). + // Separated from canonicalization for clarity. + check_submdspan_slice_mandates( + std::make_index_sequence(), exts, slices...); + + // Actual canonicalization: returns detail::tuple for device compatibility. + return detail::tuple{ + submdspan_canonicalize_one_slice( + exts, + get_kth_in_pack(slices...) + )... + }; +} + +} // namespace detail + +// ============================================================ +// submdspan_canonicalize_slices: public API +// +// Given an extents object and a pack of slice specifiers, +// returns a detail::tuple of canonical slice specifiers. +// Each canonical slice is one of: +// - full_extent_t (for full-extent slices) +// - IndexType (for integer index slices) +// - strided_slice<...> (for range and strided-range slices) +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + size_t... Extents, + class... Slices, + /* requires */ (sizeof...(Slices) == sizeof...(Extents)) +) +MDSPAN_INLINE_FUNCTION +constexpr auto submdspan_canonicalize_slices( + const extents& exts, + Slices&&... slices) +{ + return detail::submdspan_canonicalize_slices_impl( + std::make_index_sequence(), exts, slices...); +} + +#endif // MDSPAN_HAS_CXX_17 + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1693ac65..eb87423d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -99,6 +99,7 @@ if(NOT CMAKE_CXX_STANDARD STREQUAL "14") mdspan_add_test(test_submdspan_static_slice) mdspan_add_test(test_layout_padded_left ENABLE_PRECONDITIONS) mdspan_add_test(test_layout_padded_right ENABLE_PRECONDITIONS) + mdspan_add_test(test_canonicalize_slices) endif() # both of those don't work yet since its using vector if(NOT MDSPAN_ENABLE_CUDA AND NOT MDSPAN_ENABLE_HIP) diff --git a/tests/test_canonicalize_slices.cpp b/tests/test_canonicalize_slices.cpp new file mode 100644 index 00000000..d85a9507 --- /dev/null +++ b/tests/test_canonicalize_slices.cpp @@ -0,0 +1,332 @@ +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER +#include +#include +#include + +#include + +namespace my_test { + +// Aggregate pair type (supports structured binding natively as aggregate) +template +struct my_aggregate_pair { + First first; + Second second; +}; + +// Non-aggregate pair type (uses the tuple protocol) +template +class my_nonaggregate_pair { +public: + constexpr my_nonaggregate_pair(First first, Second second) + : first_(first), second_(second) + {} + + template + constexpr auto get() -> std::conditional_t { + if constexpr (Index == 0) { + return first_; + } + else { + static_assert(Index == 1); + return second_; + } + } + + template + constexpr auto get() const -> std::conditional_t { + if constexpr (Index == 0) { + return first_; + } + else { + static_assert(Index == 1); + return second_; + } + } + +private: + First first_; + Second second_; +}; + +} // namespace my_test + +template +struct std::tuple_size> + : std::integral_constant {}; + +template +struct std::tuple_element> { + static_assert(Index == 0 || Index == 1, "Index out of range"); +}; + +template +struct std::tuple_element<0, my_test::my_nonaggregate_pair> { + using type = First; +}; + +template +struct std::tuple_element<1, my_test::my_nonaggregate_pair> { + using type = Second; +}; + +template +constexpr auto get(my_test::my_nonaggregate_pair& p) + -> std::conditional_t +{ + return p.template get(); +} + +template +constexpr auto get(const my_test::my_nonaggregate_pair& p) + -> std::conditional_t +{ + return p.template get(); +} + +namespace { + +// ============================================================ +// Helpers for comparing slices +// ============================================================ + +// full_extent_t lacks operator==; two full_extent_t values are always equal +constexpr bool slice_equal( + MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t, + MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t) +{ + return true; +} + +template +constexpr bool slice_equal( + const MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice& left, + const MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice& right) +{ + return left.offset == right.offset && + left.extent == right.extent && + left.stride == right.stride; +} + +// Integer (index) slices: compare by value +template, int> = 0> +constexpr bool slice_equal(T left, T right) { + return left == right; +} + +// Integral-constant-like slices: compare their values +template && + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::is_integral_constant_like_v, + int> = 0> +constexpr bool slice_equal(T, U) { + return T::value == U::value; +} + +// ============================================================ +// Test helpers: compare the k-th element of result vs expected +// ============================================================ + +template +void test_canonicalize_slices_impl_one( + std::integral_constant, + const Result& result, + const ExpectedResult& expected_result) +{ + // Use ADL to find the right get<>() for each tuple type + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::get; + using std::get; + auto left = get(result); + auto right = get(expected_result); + const bool outcome = slice_equal(left, right); + ASSERT_TRUE(outcome) << " failed for k=" << Index; +} + +template +void test_canonicalize_slices_impl( + std::index_sequence, + const Result& result, + const ExpectedResult& expected_result) +{ + (test_canonicalize_slices_impl_one( + std::integral_constant{}, result, expected_result), ...); +} + +template +void test_canonicalize_slices( + const ExpectedResult& expected_result, + const InputExtents& input_extents, + Slices... slices) +{ + auto result = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan_canonicalize_slices( + input_extents, slices...); + test_canonicalize_slices_impl( + std::make_index_sequence(), result, expected_result); +} + +// ============================================================ +// Tests +// ============================================================ + +TEST(CanonicalizeSlices, Rank0) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + test_canonicalize_slices(tuple{}, MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}); + test_canonicalize_slices(tuple{}, MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}); +} + +TEST(CanonicalizeSlices, Rank1_full) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + constexpr auto full = MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent; + const auto expected_result = tuple{full}; + test_canonicalize_slices(expected_result, + MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}, full); + test_canonicalize_slices(expected_result, + MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}, + full); +} + +TEST(CanonicalizeSlices, Rank1_integer_dynamic) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::cw; + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + + constexpr auto slice0 = int(7); + constexpr auto expected_slices = tuple{size_t(7u)}; + constexpr auto exts = MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}; + test_canonicalize_slices(expected_slices, exts, slice0); +} + +TEST(CanonicalizeSlices, Rank1_integer_static) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::cw; + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + + constexpr auto slice0 = std::integral_constant{}; + constexpr auto expected_slices = tuple{cw}; + constexpr auto exts = MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}; + test_canonicalize_slices(expected_slices, exts, slice0); +} + +TEST(CanonicalizeSlices, Rank1_pair) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::cw; + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + + // std::pair with static first element, dynamic second + constexpr auto slice0 = std::pair{std::integral_constant{}, 11}; + + constexpr auto offset = cw; + constexpr auto extent = size_t(4u); // 11 - 7 + constexpr auto stride = cw; + + const auto expected_slices = tuple{ + MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice< + decltype(offset), decltype(extent), decltype(stride) + >{offset, extent, stride} + }; + constexpr auto exts = MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}; + test_canonicalize_slices(expected_slices, exts, slice0); +} + +TEST(CanonicalizeSlices, Rank1_aggregate_pair) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::cw; + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + + constexpr auto slice0 = my_test::my_aggregate_pair{7, 11}; + + constexpr auto offset = size_t(7u); + constexpr auto extent = size_t(4u); // 11 - 7 + constexpr auto stride = cw; + + const auto expected_slices = tuple{ + MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice< + decltype(offset), decltype(extent), decltype(stride) + >{offset, extent, stride} + }; + constexpr auto exts = MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}; + test_canonicalize_slices(expected_slices, exts, slice0); +} + +TEST(CanonicalizeSlices, Rank1_nonaggregate_pair) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::cw; + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + + auto slice0 = my_test::my_nonaggregate_pair(7, 11); + + constexpr auto offset = size_t(7u); + constexpr auto extent = size_t(4u); // 11 - 7 + constexpr auto stride = cw; + + const auto expected_slices = tuple{ + MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice< + decltype(offset), decltype(extent), decltype(stride) + >{offset, extent, stride} + }; + constexpr auto exts = MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}; + test_canonicalize_slices(expected_slices, exts, slice0); +} + +TEST(CanonicalizeSlices, Rank2_full) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + constexpr auto full = MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent; + const auto expected_result = tuple{full, full}; + test_canonicalize_slices(expected_result, + MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}, full, full); +} + +TEST(CanonicalizeSlices, Rank1_strided_slice_dynamic) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::cw; + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + + // strided_slice with all dynamic values + const auto slice0 = MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice{ + /* .offset = */ 1, + /* .extent = */ 4, + /* .stride = */ 2 + }; + + const auto expected_slices = tuple{ + MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice{ + size_t(1), size_t(4), size_t(2) + } + }; + const auto exts = MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}; + test_canonicalize_slices(expected_slices, exts, slice0); +} + +TEST(CanonicalizeSlices, Rank1_strided_slice_static) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::cw; + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple; + + using offset_t = std::integral_constant; + using extent_t = std::integral_constant; + using stride_t = std::integral_constant; + const auto slice0 = + MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice{}; + + const auto expected_slices = tuple{ + MDSPAN_IMPL_STANDARD_NAMESPACE::strided_slice< + decltype(cw), + decltype(cw), + decltype(cw) + >{cw, cw, cw} + }; + const auto exts = MDSPAN_IMPL_STANDARD_NAMESPACE::extents{}; + test_canonicalize_slices(expected_slices, exts, slice0); +} + +} // namespace (anonymous) From 68134d110e120d949d3f81195674bdb85b308a58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:12:08 +0000 Subject: [PATCH 3/8] Simplify canonicalize_slices: remove unused params, eliminate get_kth_in_pack Co-authored-by: crtrott <9490481+crtrott@users.noreply.github.com> --- .../submdspan_canonicalize_slices.hpp | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp index f524bcf2..ab732f56 100644 --- a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -303,22 +303,6 @@ constexpr check_static_bounds_result check_static_bounds( } } -// ============================================================ -// get_kth_in_pack: get the k-th element of a parameter pack -// ============================================================ - -template -MDSPAN_INLINE_FUNCTION -constexpr decltype(auto) get_kth_in_pack(First&& first, Rest&&... rest) { - static_assert(k <= sizeof...(Rest)); - if constexpr (k == 0) { - return std::forward(first); - } - else { - return get_kth_in_pack(std::forward(rest)...); - } -} - // ============================================================ // check_submdspan_slice_mandate: mandate check for the k-th slice // @@ -352,22 +336,23 @@ constexpr void check_submdspan_slice_mandates( const extents& exts, Slices... slices) { - (check_submdspan_slice_mandate(exts, get_kth_in_pack(slices...)), ...); + (check_submdspan_slice_mandate(exts, slices), ...); } // ============================================================ -// submdspan_canonicalize_one_slice: canonicalize the k-th slice +// submdspan_canonicalize_one_slice: canonicalize a single slice // // This function performs ONLY the conversion to canonical form. // Mandate checking (static_asserts) is NOT done here; it is // done separately by check_submdspan_slice_mandates. +// +// Templated only on IndexType (the extents index type) and Slice. +// Neither k nor the extents are needed for the actual conversion. // ============================================================ -template +template MDSPAN_INLINE_FUNCTION -constexpr auto submdspan_canonicalize_one_slice( - [[maybe_unused]] const extents& exts, - [[maybe_unused]] Slice s) +constexpr auto submdspan_canonicalize_one_slice([[maybe_unused]] Slice s) { if constexpr (std::is_convertible_v) { return full_extent; // canonical full-extent slice @@ -447,10 +432,7 @@ constexpr auto submdspan_canonicalize_slices_impl( // Actual canonicalization: returns detail::tuple for device compatibility. return detail::tuple{ - submdspan_canonicalize_one_slice( - exts, - get_kth_in_pack(slices...) - )... + submdspan_canonicalize_one_slice(slices)... }; } From 594eabc43621708bc8ac37601580de047feb36aa Mon Sep 17 00:00:00 2001 From: Christian Trott Date: Wed, 3 Jun 2026 13:15:23 -0600 Subject: [PATCH 4/8] Rename submdspan_canonicalize_slices to align with ISO C++ Signed-off-by: Christian Trott --- .../submdspan_canonicalize_slices.hpp | 44 ++++++++++--------- tests/test_canonicalize_slices.cpp | 2 +- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp index ab732f56..0556868c 100644 --- a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -78,7 +78,7 @@ constexpr auto index_cast(OtherIndexType&& i) noexcept { } // ============================================================ -// canonical_ice: canonicalize a value to IndexType, +// canonical_index: canonicalize a value to IndexType, // preserving integral-constant nature when possible // ============================================================ @@ -88,13 +88,15 @@ MDSPAN_TEMPLATE_REQUIRES( /* requires */ (std::is_convertible_v) ) MDSPAN_INLINE_FUNCTION -constexpr auto canonical_ice([[maybe_unused]] S s) { +constexpr auto canonical_index([[maybe_unused]] S s) { + // TODO: might move to public semi/public only to get error earlier, and + // don't duplicate check static_assert(std::is_signed_v || std::is_unsigned_v); if constexpr (is_integral_constant_like_v) { return cw(index_cast(S::value))>; } else { - return static_cast(index_cast(s)); + return static_cast(index_cast(std::move(s))); } } @@ -110,10 +112,10 @@ constexpr auto subtract_ice([[maybe_unused]] X x, [[maybe_unused]] Y y) { is_integral_constant_like_v> && is_integral_constant_like_v>) { - return cw(Y::value) - canonical_ice(X::value))>; + return cw(Y::value) - canonical_index(X::value))>; } else { - return canonical_ice(y) - canonical_ice(x); + return canonical_index(y) - canonical_index(x); } } @@ -307,7 +309,7 @@ constexpr check_static_bounds_result check_static_bounds( // check_submdspan_slice_mandate: mandate check for the k-th slice // // Contains only static_asserts; no actual computation. -// Separated from submdspan_canonicalize_one_slice so that +// Separated from canonical_slice so that // mandate checking and canonicalization are distinct concerns. // ============================================================ @@ -340,7 +342,7 @@ constexpr void check_submdspan_slice_mandates( } // ============================================================ -// submdspan_canonicalize_one_slice: canonicalize a single slice +// canonical_slice: canonicalize a single slice // // This function performs ONLY the conversion to canonical form. // Mandate checking (static_asserts) is NOT done here; it is @@ -352,19 +354,19 @@ constexpr void check_submdspan_slice_mandates( template MDSPAN_INLINE_FUNCTION -constexpr auto submdspan_canonicalize_one_slice([[maybe_unused]] Slice s) +constexpr auto canonical_slice([[maybe_unused]] Slice s) { if constexpr (std::is_convertible_v) { return full_extent; // canonical full-extent slice } else if constexpr (std::is_convertible_v) { - return canonical_ice(s); // canonical integer index + return canonical_index(std::move(s)); // canonical integer index } else if constexpr (is_strided_slice::value) { // Canonicalize each component of the strided_slice - auto offset = canonical_ice(s.offset); - auto extent = canonical_ice(s.extent); - auto stride = canonical_ice(s.stride); + auto offset = canonical_index(s.offset); + auto extent = canonical_index(s.extent); + auto stride = canonical_index(s.stride); return strided_slice{ /* .offset = */ offset, /* .extent = */ extent, @@ -374,8 +376,8 @@ constexpr auto submdspan_canonicalize_one_slice([[maybe_unused]] Slice s) #if ! defined(__cpp_lib_tuple_like) || (__cpp_lib_tuple_like < 202311L) else if constexpr (is_std_complex) { // std::complex used as [real, imag) range (offset, offset+extent) - auto offset = canonical_ice(s.real()); - auto extent = canonical_ice(s.imag() - s.real()); + auto offset = canonical_index(s.real()); + auto extent = canonical_index(s.imag() - s.real()); auto stride = cw; return strided_slice{ /* .offset = */ offset, @@ -392,7 +394,7 @@ constexpr auto submdspan_canonicalize_one_slice([[maybe_unused]] Slice s) static_assert(std::is_convertible_v); static_assert(std::is_convertible_v); - auto offset = canonical_ice(s_k0); + auto offset = canonical_index(s_k0); auto extent = subtract_ice(s_k0, s_k1); auto stride = cw; return strided_slice{ @@ -404,7 +406,7 @@ constexpr auto submdspan_canonicalize_one_slice([[maybe_unused]] Slice s) } // ============================================================ -// submdspan_canonicalize_slices_impl: implementation helper +// canonical_slices_impl: implementation helper // // First performs mandate checks (static_asserts), then // returns a detail::tuple of canonical slices. @@ -420,7 +422,7 @@ MDSPAN_TEMPLATE_REQUIRES( /* requires */ (sizeof...(Slices) == sizeof...(Extents)) ) MDSPAN_INLINE_FUNCTION -constexpr auto submdspan_canonicalize_slices_impl( +constexpr auto canonical_slices_impl( std::index_sequence, const extents& exts, Slices... slices) @@ -432,7 +434,7 @@ constexpr auto submdspan_canonicalize_slices_impl( // Actual canonicalization: returns detail::tuple for device compatibility. return detail::tuple{ - submdspan_canonicalize_one_slice(slices)... + canonical_slice(slices)... }; } @@ -456,11 +458,11 @@ MDSPAN_TEMPLATE_REQUIRES( /* requires */ (sizeof...(Slices) == sizeof...(Extents)) ) MDSPAN_INLINE_FUNCTION -constexpr auto submdspan_canonicalize_slices( +constexpr auto canonical_slices( const extents& exts, - Slices&&... slices) + Slices... slices) { - return detail::submdspan_canonicalize_slices_impl( + return detail::canonical_slices_impl( std::make_index_sequence(), exts, slices...); } diff --git a/tests/test_canonicalize_slices.cpp b/tests/test_canonicalize_slices.cpp index d85a9507..9f65e23a 100644 --- a/tests/test_canonicalize_slices.cpp +++ b/tests/test_canonicalize_slices.cpp @@ -174,7 +174,7 @@ void test_canonicalize_slices( const InputExtents& input_extents, Slices... slices) { - auto result = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan_canonicalize_slices( + auto result = MDSPAN_IMPL_STANDARD_NAMESPACE::canonical_slices( input_extents, slices...); test_canonicalize_slices_impl( std::make_index_sequence(), result, expected_result); From b46f00f9ed1418d01f42419227ebe45dff06db6c Mon Sep 17 00:00:00 2001 From: Christian Trott Date: Wed, 3 Jun 2026 13:45:23 -0600 Subject: [PATCH 5/8] Remvoe complex support in canonical slice Signed-off-by: Christian Trott --- .../submdspan_canonicalize_slices.hpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp index 0556868c..bffaf57c 100644 --- a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -367,26 +367,13 @@ constexpr auto canonical_slice([[maybe_unused]] Slice s) auto offset = canonical_index(s.offset); auto extent = canonical_index(s.extent); auto stride = canonical_index(s.stride); + // TODO: Later introduce canonical-range-slice return strided_slice{ /* .offset = */ offset, /* .extent = */ extent, /* .stride = */ stride }; - } -#if ! defined(__cpp_lib_tuple_like) || (__cpp_lib_tuple_like < 202311L) - else if constexpr (is_std_complex) { - // std::complex used as [real, imag) range (offset, offset+extent) - auto offset = canonical_index(s.real()); - auto extent = canonical_index(s.imag() - s.real()); - auto stride = cw; - return strided_slice{ - /* .offset = */ offset, - /* .extent = */ extent, - /* .stride = */ stride - }; - } -#endif - else { + } else { // General pair-like case: structured binding into [first, last) auto [s_k0, s_k1] = s; using S_k0 = decltype(s_k0); @@ -397,6 +384,7 @@ constexpr auto canonical_slice([[maybe_unused]] Slice s) auto offset = canonical_index(s_k0); auto extent = subtract_ice(s_k0, s_k1); auto stride = cw; + // TODO: Later introduce canonical-range-slice return strided_slice{ /* .offset = */ offset, /* .extent = */ extent, From 3643cca6cde016eec2dbb488432e96cecfc53c57 Mon Sep 17 00:00:00 2001 From: Christian Trott Date: Wed, 3 Jun 2026 14:16:21 -0600 Subject: [PATCH 6/8] More cleanup Signed-off-by: Christian Trott --- .../submdspan_canonicalize_slices.hpp | 153 +++++------------- 1 file changed, 42 insertions(+), 111 deletions(-) diff --git a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp index bffaf57c..8cb7bd80 100644 --- a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -74,7 +74,7 @@ MDSPAN_TEMPLATE_REQUIRES( ) MDSPAN_INLINE_FUNCTION constexpr auto index_cast(OtherIndexType&& i) noexcept { - return static_cast(i); + return static_cast(std::forward(i)); } // ============================================================ @@ -91,6 +91,8 @@ MDSPAN_INLINE_FUNCTION constexpr auto canonical_index([[maybe_unused]] S s) { // TODO: might move to public semi/public only to get error earlier, and // don't duplicate check + // TODO: add mandate for integral-constant-like representable as IndexType + // TODO: add precondition check that index-cast is representable as IndexType static_assert(std::is_signed_v || std::is_unsigned_v); if constexpr (is_integral_constant_like_v) { return cw(index_cast(S::value))>; @@ -119,81 +121,34 @@ constexpr auto subtract_ice([[maybe_unused]] X x, [[maybe_unused]] Y y) { } } -// ============================================================ -// check_static_bounds_result: result of a compile-time bounds check -// ============================================================ - -enum class check_static_bounds_result { - in_bounds, - out_of_bounds, - unknown -}; - -// ============================================================ -// is_std_complex: detect std::complex (for pre-tuple-like compilers) -// ============================================================ - -// Clang 21.0.0 does not define __cpp_lib_tuple_like, so it does not -// support the tuple protocol for std::complex. Interestingly, it permits -// structured binding, but decomposes it into one element, not two. -// We work around with a special canonicalization case. -#if ! defined(__cpp_lib_tuple_like) || (__cpp_lib_tuple_like < 202311L) -template -constexpr bool is_std_complex = false; -template -constexpr bool is_std_complex> = true; -#endif - // ============================================================ // check_static_bounds: compile-time bounds check for a slice // -// Returns whether the k-th slice is statically in bounds, -// out of bounds, or unknown (dynamic bounds or dynamic slice values). +// Returns false if the slice is statically out of bounds. // // This function is called only in static_assert contexts. // ============================================================ -template -constexpr check_static_bounds_result check_static_bounds( - const extents&) +template +constexpr bool check_static_bounds() { -#if defined(__cpp_pack_indexing) - constexpr size_t Exts_k = Exts...[k]; -#else - constexpr size_t Exts_k = [] () { - size_t result = 0; - size_t i = 0; - (void) ((i++ == k ? (result = Exts, true) : false) || ...); - return result; - } (); -#endif - if constexpr (std::is_convertible_v) { - return check_static_bounds_result::in_bounds; + return true; } else if constexpr (std::is_convertible_v) { if constexpr (is_integral_constant_like_v) { if constexpr (de_ice(S_k{}) < 0) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr ( Exts_k != dynamic_extent && Exts_k <= static_cast(de_ice(S_k{}))) { - return check_static_bounds_result::out_of_bounds; - } - else if constexpr ( - Exts_k != dynamic_extent && - static_cast(de_ice(S_k{})) < Exts_k) - { - return check_static_bounds_result::in_bounds; - } - else { - return check_static_bounds_result::unknown; + return false; } - } - else { - return check_static_bounds_result::unknown; + else { return true; } + } else { + return true; } } else if constexpr (is_strided_slice::value) { @@ -201,26 +156,26 @@ constexpr check_static_bounds_result check_static_bounds( if constexpr (is_integral_constant_like_v) { if constexpr (de_ice(offset_type{}) < 0) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr ( Exts_k != dynamic_extent && Exts_k < static_cast(de_ice(offset_type{}))) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr (is_integral_constant_like_v) { using extent_type = typename S_k::extent_type; if constexpr (de_ice(offset_type{}) + de_ice(extent_type{}) < 0) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr ( Exts_k != dynamic_extent && Exts_k < static_cast(de_ice(offset_type{}) + de_ice(extent_type{}))) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr ( Exts_k != dynamic_extent && @@ -230,29 +185,25 @@ constexpr check_static_bounds_result check_static_bounds( static_cast( de_ice(offset_type{}) + de_ice(extent_type{})) <= Exts_k) { - return check_static_bounds_result::in_bounds; + return true; } else { - return check_static_bounds_result::unknown; + return true; } } else { - return check_static_bounds_result::unknown; + return true; } } else { - return check_static_bounds_result::unknown; + return true; } - } -#if ! defined(__cpp_lib_tuple_like) || (__cpp_lib_tuple_like < 202311L) - else if constexpr (is_std_complex) { - return check_static_bounds_result::unknown; - } -#endif - else { + } else { // General pair-like case: attempt to get the first and second elements. // If S_k cannot be structured-bound into two elements, this is ill-formed, // which implements the Mandates clause. + // Doing this via these lambdas since we can do the declval only in a + // non-evaluated context auto get_first = [] (S_k s_k) { auto [s_k0, _x] = s_k; return s_k0; @@ -263,25 +214,26 @@ constexpr check_static_bounds_result check_static_bounds( }; using S_k0 = decltype(get_first(std::declval())); using S_k1 = decltype(get_second(std::declval())); + if constexpr (is_integral_constant_like_v) { if constexpr (de_ice(S_k0{}) < 0) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr ( Exts_k != dynamic_extent && Exts_k < static_cast(de_ice(S_k0{}))) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr (is_integral_constant_like_v) { if constexpr (de_ice(S_k1{}) < de_ice(S_k0{})) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr ( Exts_k != dynamic_extent && Exts_k < static_cast(de_ice(S_k1{}))) { - return check_static_bounds_result::out_of_bounds; + return false; } else if constexpr ( Exts_k != dynamic_extent && @@ -289,18 +241,18 @@ constexpr check_static_bounds_result check_static_bounds( de_ice(S_k0{}) <= de_ice(S_k1{}) && static_cast(de_ice(S_k1{})) <= Exts_k) { - return check_static_bounds_result::in_bounds; + return true; } else { - return check_static_bounds_result::unknown; + return true; } } else { - return check_static_bounds_result::unknown; + return true; } } else { - return check_static_bounds_result::unknown; + return true; } } } @@ -313,32 +265,13 @@ constexpr check_static_bounds_result check_static_bounds( // mandate checking and canonicalization are distinct concerns. // ============================================================ -template +template MDSPAN_INLINE_FUNCTION -constexpr void check_submdspan_slice_mandate( - const extents&, - [[maybe_unused]] Slice) +constexpr bool check_submdspan_slice_mandate( + [[maybe_unused]] const Slice&) { - static_assert( - check_static_bounds(extents{}) != - check_static_bounds_result::out_of_bounds); -} - -// ============================================================ -// check_submdspan_slice_mandates: mandate check for all slices -// -// Calls check_submdspan_slice_mandate for each slice. -// Separated from canonicalization so mandate checking is explicit. -// ============================================================ - -template -MDSPAN_INLINE_FUNCTION -constexpr void check_submdspan_slice_mandates( - std::index_sequence, - const extents& exts, - Slices... slices) -{ - (check_submdspan_slice_mandate(exts, slices), ...); + static_assert(check_static_bounds()); + return true; } // ============================================================ @@ -404,25 +337,23 @@ constexpr auto canonical_slice([[maybe_unused]] Slice s) MDSPAN_TEMPLATE_REQUIRES( size_t... Inds, - class IndexType, - size_t... Extents, + class Extents, class... Slices, - /* requires */ (sizeof...(Slices) == sizeof...(Extents)) + /* requires */ (sizeof...(Slices) == Extents::rank()) ) MDSPAN_INLINE_FUNCTION constexpr auto canonical_slices_impl( std::index_sequence, - const extents& exts, + const Extents&, Slices... slices) { // Mandate checks (static_asserts only, no computation). // Separated from canonicalization for clarity. - check_submdspan_slice_mandates( - std::make_index_sequence(), exts, slices...); + (void)(check_submdspan_slice_mandate(slices) && ... && true); // Actual canonicalization: returns detail::tuple for device compatibility. return detail::tuple{ - canonical_slice(slices)... + canonical_slice(slices)... }; } From 309f7008418098703916c7c84ea69a5da9c8d86b Mon Sep 17 00:00:00 2001 From: Christian Trott Date: Fri, 26 Jun 2026 11:48:11 -0600 Subject: [PATCH 7/8] Add one more move Signed-off-by: Christian Trott --- .../experimental/__p2630_bits/submdspan_canonicalize_slices.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp index 8cb7bd80..0e5a60e1 100644 --- a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -308,7 +308,7 @@ constexpr auto canonical_slice([[maybe_unused]] Slice s) }; } else { // General pair-like case: structured binding into [first, last) - auto [s_k0, s_k1] = s; + auto [s_k0, s_k1] = std::move(s); using S_k0 = decltype(s_k0); using S_k1 = decltype(s_k1); static_assert(std::is_convertible_v); From b6b073ed970bba944b4f3895d901ca5206ad61b8 Mon Sep 17 00:00:00 2001 From: Christian Trott Date: Fri, 26 Jun 2026 15:15:42 -0600 Subject: [PATCH 8/8] Fix some comments Signed-off-by: Christian Trott --- .../__p2630_bits/submdspan_canonicalize_slices.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp index 0e5a60e1..84e91351 100644 --- a/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -300,7 +300,6 @@ constexpr auto canonical_slice([[maybe_unused]] Slice s) auto offset = canonical_index(s.offset); auto extent = canonical_index(s.extent); auto stride = canonical_index(s.stride); - // TODO: Later introduce canonical-range-slice return strided_slice{ /* .offset = */ offset, /* .extent = */ extent, @@ -317,7 +316,6 @@ constexpr auto canonical_slice([[maybe_unused]] Slice s) auto offset = canonical_index(s_k0); auto extent = subtract_ice(s_k0, s_k1); auto stride = cw; - // TODO: Later introduce canonical-range-slice return strided_slice{ /* .offset = */ offset, /* .extent = */ extent, @@ -360,7 +358,7 @@ constexpr auto canonical_slices_impl( } // namespace detail // ============================================================ -// submdspan_canonicalize_slices: public API +// canonicalize_slices: public API // // Given an extents object and a pack of slice specifiers, // returns a detail::tuple of canonical slice specifiers.