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..ab732f56 --- /dev/null +++ b/include/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -0,0 +1,469 @@ +//@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; + } + } +} + +// ============================================================ +// 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, slices), ...); +} + +// ============================================================ +// 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 +MDSPAN_INLINE_FUNCTION +constexpr auto submdspan_canonicalize_one_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 + } + 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(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)