diff --git a/docs/sphinx/user_guide/feature/iteration_spaces.rst b/docs/sphinx/user_guide/feature/iteration_spaces.rst index aeba1da60d..166c59d03f 100644 --- a/docs/sphinx/user_guide/feature/iteration_spaces.rst +++ b/docs/sphinx/user_guide/feature/iteration_spaces.rst @@ -94,3 +94,75 @@ and two types: Thus, any iterable type that defines these methods and types appropriately can be used as a segment with RAJA kernel execution templates. + +Python-like Range Helpers +^^^^^^^^^^^^^^^^^^^^^^^^^ + +RAJA also provides ``RAJA::range(...)`` helpers that construct the segment +type for common half-open iteration patterns. These helpers are intended to +mirror the shape of Python's ``range`` while returning RAJA segment objects +that can be passed directly to ``RAJA::forall`` and other execution +interfaces. + +The supported forms are:: + + RAJA::range(end) // [0, end) + RAJA::range(end) // [0, end) with explicit storage type + RAJA::range(begin, end) // [begin, end) + RAJA::range(begin, end, stride) // [begin, end) with stride + +The return type depends on the arguments: + +* ``RAJA::range(end)`` and ``RAJA::range(begin, end)`` return a + ``RAJA::TypedRangeSegment``. +* ``RAJA::range(begin, end, stride)`` returns a + ``RAJA::TypedRangeStrideSegment``. +* When one of the bounds is a RAJA strong index type, such as a type created + with ``RAJA_INDEX_VALUE``, all bounds must use that same strong type. Mixed + strong and plain integral bounds are rejected rather than narrowed. +* Providing an explicit template argument, such as + ``RAJA::range(end)``, overrides the deduced storage type, but + explicit storage must still match the argument types. For example, + ``RAJA::range(RangeStrongIndex(3), 17)`` and + ``RAJA::range(AnotherRangeStrongIndex(3), 17)`` are + rejected, while ``RAJA::range(RangeStrongIndex(3), + RangeStrongIndex(17))`` is valid. + +For example:: + + RAJA_INDEX_VALUE(CellIndex, "CellIndex"); + + RAJA::forall(RAJA::range(N), [=](RAJA::Index_type i) { + values[i] = i * i; + }); + + RAJA::forall(RAJA::range(CellIndex {N}), + [=](CellIndex i) { + typed_values[*i] = *i + 10; + }); + + RAJA::forall(RAJA::range(2, 6), [=](int i) { + subrange_values[i] = i; + }); + + RAJA::forall(RAJA::range(CellIndex {1}, CellIndex {N}, + CellIndex {2}), + [=](CellIndex i) { + strided_values[*i] = *i; + }); + +Strided ranges follow the same half-open interval convention as +``RAJA::TypedRangeStrideSegment``. Positive strides move forward, and negative +strides move backward. For example, ``RAJA::range(N - 1, -1, -2)`` visits +``N - 1, N - 3, ...`` down to the first value that remains greater than +``-1``. A zero stride is invalid and causes RAJA to abort or throw, depending +on the build configuration. + +The older ``RAJA::make_range`` and ``RAJA::make_strided_range`` helpers remain +available. Use ``RAJA::range(...)`` when the Python-like spelling improves +readability or when you want the one-argument ``[0, end)`` shorthand. + +The complete example added in this branch is shown below: + +.. literalinclude:: ../../../../examples/raja-ranges.cpp + :language: c++ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e77b5dc493..24d3831f5b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,6 +19,10 @@ raja_add_executable( NAME resource-forall SOURCES resource-forall.cpp) +raja_add_executable( + NAME raja-ranges + SOURCES raja-ranges.cpp) + raja_add_executable( NAME dynamic-forall SOURCES dynamic-forall.cpp) diff --git a/examples/raja-ranges.cpp b/examples/raja-ranges.cpp new file mode 100644 index 0000000000..eb217a048f --- /dev/null +++ b/examples/raja-ranges.cpp @@ -0,0 +1,114 @@ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +// Copyright (c) Lawrence Livermore National Security, LLC and other +// RAJA Project Developers. See top-level LICENSE and COPYRIGHT +// files for dates and other details. No copyright assignment is required +// to contribute to RAJA. +// +// SPDX-License-Identifier: (BSD-3-Clause) +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + +#include + +#include "RAJA/RAJA.hpp" + +/* + * RAJA ranges example + * + * Demonstrates Python-like range helpers backed by RAJA segments: + * 1. RAJA::range(N) for [0, N) + * 2. RAJA::range(N) for a TypedRangeSegment over [0, N) + * 3. RAJA::range(begin, end) for [begin, end) + * 4. RAJA::range(begin, end, step) for a strided half-open interval + * 5. Strong-index ranges using consistent strong types + */ + +RAJA_INDEX_VALUE(CellIndex, "CellIndex"); + +int main(int RAJA_UNUSED_ARG(argc), char** RAJA_UNUSED_ARG(argv)) +{ + constexpr RAJA::Index_type N = 8; + + int values[N] = {}; + int typed_values[N] = {}; + int subrange_values[N] = {}; + int odd_values[N] = {}; + int strong_end_values[N] = {}; + int strong_stride_values[N] = {}; + + // Equivalent to Python range(N): [0, N) + RAJA::forall(RAJA::range(N), [&](RAJA::Index_type i) { + values[i] = static_cast(i * i); + }); + + // Equivalent to Python range(N), but preserving a strong index type. + RAJA::forall( + RAJA::range(CellIndex {N}), [&](CellIndex i) { + typed_values[*i] = static_cast(*i + 10); + }); + + // Equivalent to Python range(2, 6): [2, 6) + RAJA::forall(RAJA::range(2, 6), [&](int i) { + subrange_values[i] = i; + }); + + // Equivalent to Python range(1, N, 2): odd indices in [1, N) + RAJA::forall(RAJA::range(1, N, 2), [&](int i) { + odd_values[i] = i; + }); + + // Strong-index range with consistent strong bounds. + RAJA::forall( + RAJA::range(CellIndex {1}, CellIndex {6}), + [&](CellIndex i) { strong_end_values[*i] = static_cast(*i * 10); }); + + // Strong-index strided range with consistent strong bounds. + RAJA::forall( + RAJA::range(CellIndex {1}, CellIndex {N}, CellIndex {2}), + [&](CellIndex i) { + strong_stride_values[*i] = static_cast(*i * 100); + }); + + std::cout << "range(N):"; + for (auto i : RAJA::range(N)) { + std::cout << ' ' << values[i]; + } + std::cout << '\n'; + + std::cout << "range(CellIndex{N}):"; + for (auto i : RAJA::range(CellIndex {N})) { + std::cout << ' ' << typed_values[*i]; + } + std::cout << '\n'; + + std::cout << "range(2, 6):"; + for (auto i : RAJA::range(2, 6)) { + std::cout << ' ' << subrange_values[i]; + } + std::cout << '\n'; + + std::cout << "range(1, N, 2):"; + for (auto i : RAJA::range(1, N, 2)) { + std::cout << ' ' << odd_values[i]; + } + std::cout << '\n'; + + std::cout << "range(CellIndex{1}, CellIndex{6}):"; + for (auto i : RAJA::range(CellIndex {1}, CellIndex {6})) { + std::cout << ' ' << strong_end_values[*i]; + } + std::cout << '\n'; + + std::cout << "range(CellIndex{1}, CellIndex{N}, CellIndex{2}):"; + for (auto i : RAJA::range(CellIndex {1}, CellIndex {N}, CellIndex {2})) { + std::cout << ' ' << strong_stride_values[*i]; + } + std::cout << '\n'; + + std::cout << "range(N - 1, -1, -2):"; + for (auto i : RAJA::range(N - 1, -1, -2)) { + std::cout << ' ' << i; + } + std::cout << '\n'; + + return 0; +} diff --git a/include/RAJA/index/RangeSegment.hpp b/include/RAJA/index/RangeSegment.hpp index bb91cfcc00..9865830579 100644 --- a/include/RAJA/index/RangeSegment.hpp +++ b/include/RAJA/index/RangeSegment.hpp @@ -23,6 +23,7 @@ #include "RAJA/config.hpp" #include +#include #include "RAJA/internal/Iterators.hpp" @@ -103,9 +104,9 @@ struct TypedRangeSegment // Static asserts to provide some useful error messages during compilation // for incorrect usage. // - static_assert(std::is_signed::value, + static_assert(std::is_signed_v, "TypedRangeSegment DiffT requires signed type."); - static_assert(!std::is_floating_point::value, + static_assert(!std::is_floating_point_v, "TypedRangeSegment Type must be non floating point."); //@{ @@ -336,9 +337,9 @@ struct TypedRangeStrideSegment // Static asserts to provide some useful error messages during compilation // for incorrect usage. // - static_assert(std::is_signed::value, + static_assert(std::is_signed_v, "TypedRangeStrideSegment DiffT requires signed type."); - static_assert(!std::is_floating_point::value, + static_assert(!std::is_floating_point_v, "TypedRangeStrideSegment Type must be non floating point."); //@{ @@ -533,22 +534,367 @@ using RangeStrideSegment = TypedRangeStrideSegment; namespace detail { -template -struct common_type - : std::common_type::type> +template +using common_type_t = std::common_type_t; + +struct no_strong_index {}; template -struct common_type +using strong_index_candidate_t = + std::conditional_t>, + std::decay_t, + no_strong_index>; + +template +struct strong_index_type { - using type = T; + using type = no_strong_index; +}; + +template +struct strong_index_type +{ + using current = strong_index_candidate_t; + using rest = typename strong_index_type::type; + + static_assert( + std::is_same_v || + std::is_same_v || + std::is_same_v, + "range requires matching strong index types for begin and end."); + + using type = std:: + conditional_t, current, rest>; }; template -using common_type_t = typename common_type::type; +using strong_index_type_t = typename strong_index_type::type; + +template +inline constexpr bool is_strong_index_v = + std::is_base_of_v>; + +template +inline constexpr bool is_non_strong_integral_index_v = + !is_strong_index_v && + std::is_integral_v>>; + +template +using range_stride_type_t = make_signed_t>>; + +template +struct selected_range_storage +{ + using type = StorageT; +}; + +template +struct selected_range_storage +{ + using type = DeducedT; +}; + +template +using selected_range_storage_t = + typename selected_range_storage::type; + +template +struct range_storage_from_strong +{ + using type = StrongT; +}; + +template +struct range_storage_from_strong +{ + using type = common_type_t; +}; + +template +struct strong_range_arg_compatible + : std::bool_constant> && + std::is_same_v, std::decay_t>> +{}; + +template +inline constexpr bool strong_range_arg_compatible_v = + strong_range_arg_compatible::value; + +template +struct range_storage_from_strong_compatible + : std::bool_constant || + (strong_range_arg_compatible_v && ...)> +{}; + +template + requires(std::is_same_v || + (strong_range_arg_compatible_v && ...)) +struct range_storage_from_strong +{ + using type = StrongT; +}; + +template +struct range_stride_storage_from_strong +{ + using type = StrongT; +}; + +template +struct range_stride_storage_from_strong +{ + using type = common_type_t>; +}; + +template +struct range_stride_storage_from_strong_compatible + : std::bool_constant || + (strong_range_arg_compatible_v && + strong_range_arg_compatible_v && + strong_range_arg_compatible_v)> +{}; + +template +struct range_stride_storage_from_strong_compatible_for_range + : std::bool_constant< + std::is_same_v + ? !is_strong_index_v + : ((strong_range_arg_compatible_v || + is_non_strong_integral_index_v) && + (strong_range_arg_compatible_v || + is_non_strong_integral_index_v) && + (strong_range_arg_compatible_v || + is_non_strong_integral_index_v))> +{}; + +template + requires(std::is_same_v || + ((strong_range_arg_compatible_v || + is_non_strong_integral_index_v) && + (strong_range_arg_compatible_v || + is_non_strong_integral_index_v) && + (strong_range_arg_compatible_v || + is_non_strong_integral_index_v))) +struct range_stride_storage_from_strong +{ + using type = StrongT; +}; + +template +using deduced_range_storage_type_t = + typename range_storage_from_strong, + BeginT, + EndT>::type; + +template +using deduced_range_stride_storage_type_t = + typename range_stride_storage_from_strong, + BeginT, + EndT, + StrideT>::type; + +template +using deduced_range_stride_diff_type_t = + common_type_t>, + range_stride_type_t>; + +template>, + bool ArgIsStrong = std::is_base_of_v>> +struct explicit_range_arg_compatible_impl : std::false_type +{}; + +template +struct explicit_range_arg_compatible_impl + : std::true_type +{}; + +template +struct explicit_range_arg_compatible_impl + : std::bool_constant< + std::is_same_v, std::decay_t>> +{}; + +template +using explicit_range_arg_compatible = + explicit_range_arg_compatible_impl; + +template +inline constexpr bool explicit_range_arg_compatible_v = + explicit_range_arg_compatible::value; + +template>, + bool ArgIsStrong = std::is_base_of_v>> +struct explicit_range_stride_arg_compatible_impl + : explicit_range_arg_compatible_impl +{}; + +template +struct explicit_range_stride_arg_compatible_impl + : std::bool_constant< + std::is_integral_v>>> +{}; + +template +using explicit_range_stride_arg_compatible = + explicit_range_stride_arg_compatible_impl; + +template +inline constexpr bool explicit_range_stride_arg_compatible_v = + explicit_range_stride_arg_compatible::value; + +template +struct explicit_range_storage_compatible + : std::bool_constant<(explicit_range_arg_compatible_v && ...)> +{}; + +template +struct explicit_range_stride_storage_compatible + : std::bool_constant<( + explicit_range_stride_arg_compatible_v && ...)> +{}; + +template +inline constexpr bool explicit_range_storage_compatible_v = + explicit_range_storage_compatible::value; + +template +inline constexpr bool explicit_range_stride_storage_compatible_v = + explicit_range_stride_storage_compatible::value; + +template +inline constexpr bool deduced_range_storage_compatible_v = + range_storage_from_strong_compatible, + BeginT, + EndT>::value; + +template +inline constexpr bool deduced_range_stride_storage_compatible_v = + range_stride_storage_from_strong_compatible< + strong_index_type_t, + BeginT, + EndT, + StrideT>::value; + +template +inline constexpr bool deduced_range_stride_storage_compatible_for_range_v = + range_stride_storage_from_strong_compatible_for_range< + strong_index_type_t, + BeginT, + EndT, + StrideT>::value; + +template +inline constexpr bool range_storage_compatible_v = + std::is_same_v + ? deduced_range_storage_compatible_v + : explicit_range_storage_compatible_v; + +template +inline constexpr bool range_stride_storage_compatible_v = + std::is_same_v + ? deduced_range_stride_storage_compatible_for_range_v + : explicit_range_stride_storage_compatible_v; } // namespace detail +/*! + * \brief Function to make a TypedRangeSegment for the interval [0, end) + * + * \return a newly constructed TypedRangeSegment over the half-open interval + * starting at zero and ending at @end. An explicit template argument + * may be used to select the segment storage type. + */ +template< + typename StorageT = void, + typename EndT, + typename Common = + detail::selected_range_storage_t>, + typename StripCommon = strip_index_type_t> + requires detail::range_storage_compatible_v +RAJA_HOST_DEVICE RAJA_INLINE constexpr TypedRangeSegment range( + EndT&& end) noexcept +{ + static_assert(!std::is_floating_point_v, + "range requires a non-floating point index type."); + return {StripCommon {0}, static_cast(stripIndexType(end))}; +} + +/*! + * \brief Function to make a TypedRangeSegment for the interval [begin, end) + * + * \return a newly constructed TypedRangeSegment where the + * value_type is equivilent to the common type of + * @begin and @end unless an explicit template argument + * is provided for the segment storage type. + */ +template>, + typename StripCommon = strip_index_type_t> + requires detail::range_storage_compatible_v +RAJA_HOST_DEVICE RAJA_INLINE constexpr TypedRangeSegment range( + BeginT&& begin, + EndT&& end) noexcept +{ + return {static_cast(stripIndexType(begin)), + static_cast(stripIndexType(end))}; +} + +/*! + * \brief Function to make a TypedRangeStrideSegment for the interval + * [begin, end) with given stride + * + * \return a newly constructed TypedRangeStrideSegment where the + * value_type is equivilent to the common type of + * @begin and @end unless an explicit template argument + * is provided for the segment storage type. If stride is zero, + * execution aborts or throws. + */ +template< + typename StorageT = void, + typename BeginT, + typename EndT, + typename StrideT, + typename Common = detail::selected_range_storage_t< + StorageT, + detail::deduced_range_stride_storage_type_t>, + typename DiffT = detail::deduced_range_stride_diff_type_t> + requires detail:: + range_stride_storage_compatible_v + RAJA_HOST_DEVICE RAJA_INLINE TypedRangeStrideSegment range( + BeginT&& begin, + EndT&& end, + StrideT&& stride) +{ + static_assert(std::is_integral_v>, + "range requires an integral stride type."); + + DiffT const typed_stride = static_cast(stripIndexType(stride)); + if (typed_stride == DiffT {0}) + { + RAJA_ABORT_OR_THROW("RAJA::range requires a non-zero stride."); + } + + return {static_cast>(stripIndexType(begin)), + static_cast>(stripIndexType(end)), + typed_stride}; +} + /*! * \brief Function to make a TypedRangeSegment for the interval [begin, end) * @@ -559,11 +905,14 @@ using common_type_t = typename common_type::type; */ template> + typename Common = detail::deduced_range_storage_type_t, + typename StripCommon = strip_index_type_t> + requires detail::deduced_range_storage_compatible_v RAJA_HOST_DEVICE TypedRangeSegment make_range(BeginT&& begin, EndT&& end) { - return {begin, end}; + return {static_cast(stripIndexType(begin)), + static_cast(stripIndexType(end))}; } /*! @@ -575,23 +924,64 @@ RAJA_HOST_DEVICE TypedRangeSegment make_range(BeginT&& begin, * @begin, @end, and @stride. If there is no common * type, then a compiler error will be produced. */ -template> -RAJA_HOST_DEVICE TypedRangeStrideSegment make_strided_range( - BeginT&& begin, - EndT&& end, - StrideT&& stride) +template< + typename BeginT, + typename EndT, + typename StrideT, + typename Common = + detail::deduced_range_stride_storage_type_t, + typename DiffT = detail::deduced_range_stride_diff_type_t> + requires detail:: + deduced_range_stride_storage_compatible_v + RAJA_HOST_DEVICE TypedRangeStrideSegment make_strided_range( + BeginT&& begin, + EndT&& end, + StrideT&& stride) { - static_assert(std::is_signed::value, - "make_strided_segment : stride must be signed."); - static_assert( - std::is_same, StrideT>::value, - "make_stride_segment : stride and end must be of similar types."); - return {begin, end, stride}; + static_assert(std::is_integral_v>, + "make_strided_segment : stride must be integral."); + + return {static_cast>(stripIndexType(begin)), + static_cast>(stripIndexType(end)), + static_cast(stripIndexType(stride))}; } +namespace concepts +{ + +template +concept RangeConstructible = + RAJA::detail::deduced_range_storage_compatible_v; + +template +concept RangeStrideConstructible = + RAJA::detail::deduced_range_stride_storage_compatible_for_range_v; + +} // namespace concepts + +namespace type_traits +{ + +template +struct is_range_constructible + : std::bool_constant> +{}; + +template +inline constexpr bool is_range_constructible_v = + is_range_constructible::value; + +template +struct is_range_stride_constructible + : std::bool_constant> +{}; + +template +inline constexpr bool is_range_stride_constructible_v = + is_range_stride_constructible::value; + +} // namespace type_traits + } // namespace RAJA namespace std diff --git a/test/unit/index/test-rangesegment.cpp b/test/unit/index/test-rangesegment.cpp index 7e6d8c9100..d9340810a3 100644 --- a/test/unit/index/test-rangesegment.cpp +++ b/test/unit/index/test-rangesegment.cpp @@ -15,18 +15,33 @@ #include "RAJA_unit-test-types.hpp" +#include + +RAJA_INDEX_VALUE(RangeStrongIndex, "RangeStrongIndex"); +RAJA_INDEX_VALUE(AnotherRangeStrongIndex, "AnotherRangeStrongIndex"); + +template +concept RangeCallable = requires { + RAJA::range(std::declval()...); +}; + +template +concept TypedRangeCallable = requires { + RAJA::range(std::declval()...); +}; + template class RangeSegmentUnitTest : public ::testing::Test {}; TYPED_TEST_SUITE(RangeSegmentUnitTest, UnitIndexTypes); -template< typename T, typename std::enable_if::value>::type* = nullptr> +template>* = nullptr> void NegativeRangeSegConstructorsTest() { } -template< typename T, typename std::enable_if::value>::type* = nullptr> +template>* = nullptr> void NegativeRangeSegConstructorsTest() { RAJA::TypedRangeSegment r1(-10, 7); @@ -76,12 +91,12 @@ TYPED_TEST(RangeSegmentUnitTest, Swaps) ASSERT_EQ(r2, r3); } -template< typename T, typename std::enable_if::value>::type* = nullptr> +template>* = nullptr> void NegativeRangeSegIteratorsTest() { } -template< typename T, typename std::enable_if::value>::type* = nullptr> +template>* = nullptr> void NegativeRangeSegIteratorsTest() { RAJA::TypedRangeSegment r3(-2, 100); @@ -101,14 +116,16 @@ TYPED_TEST(RangeSegmentUnitTest, Iterators) NegativeRangeSegIteratorsTest(); } -template >::value>::type* = nullptr> +template>>* = nullptr> void runNegativeIndexSliceTests() { } -template >::value>::type* = nullptr> +template>>* = nullptr> void runNegativeIndexSliceTests() { auto r1 = RAJA::TypedRangeSegment(-4, 4); @@ -166,3 +183,318 @@ TYPED_TEST(RangeSegmentUnitTest, Equality) ASSERT_NE(r1, r3); } + +TEST(RangeSegmentUnitTest, RangeEnd) +{ + auto r = RAJA::range(RAJA::Index_type(17)); + + ASSERT_EQ(RAJA::RangeSegment(RAJA::Index_type(0), RAJA::Index_type(17)), r); + ASSERT_EQ(RAJA::Index_type(0), *r.begin()); + ASSERT_EQ(RAJA::Index_type(17), r.size()); +} + +TEST(RangeSegmentUnitTest, LongRangeEnd) +{ + auto r = RAJA::range(long {10}); + + static_assert(std::is_same_v>, + "range(long) should deduce the segment storage type."); + ASSERT_EQ((RAJA::TypedRangeSegment(0, 10)), r); + ASSERT_EQ(0, *r.begin()); + ASSERT_EQ(10, r.size()); +} + +TEST(RangeSegmentUnitTest, LongRangeEndLValue) +{ + const long end = 10; + auto r = RAJA::range(end); + + static_assert(std::is_same_v>, + "range(const long&) should decay the deduced storage type."); + ASSERT_EQ((RAJA::TypedRangeSegment(0, 10)), r); + ASSERT_EQ(0, *r.begin()); + ASSERT_EQ(10, r.size()); +} + +TEST(RangeSegmentUnitTest, TypedRangeEnd) +{ + auto r = RAJA::range(RangeStrongIndex(17)); + + ASSERT_EQ((RAJA::TypedRangeSegment(0, 17)), r); + ASSERT_EQ(RangeStrongIndex(0), *r.begin()); + ASSERT_EQ(RAJA::Index_type(17), r.size()); +} + +TEST(RangeSegmentUnitTest, DeducedStrongRangeEnd) +{ + auto r = RAJA::range(RangeStrongIndex(17)); + + static_assert( + std::is_same_v>, + "range(StrongIndex) should deduce the segment storage type."); + ASSERT_EQ((RAJA::TypedRangeSegment(0, 17)), r); + ASSERT_EQ(RangeStrongIndex(0), *r.begin()); + ASSERT_EQ(RAJA::Index_type(17), r.size()); +} + +TEST(RangeSegmentUnitTest, ExplicitTypedRangeEnd) +{ + auto r = RAJA::range(10); + + static_assert(std::is_same_v>, + "range(end) should use the explicit storage type."); + ASSERT_EQ((RAJA::TypedRangeSegment(0, 10)), r); + ASSERT_EQ(0, *r.begin()); + ASSERT_EQ(10, r.size()); +} + +TEST(RangeSegmentUnitTest, RangeBeginEnd) +{ + auto r = RAJA::range(3, 17); + + ASSERT_EQ((RAJA::TypedRangeSegment(3, 17)), r); + ASSERT_EQ(14, r.size()); +} + +TEST(RangeSegmentUnitTest, StrongRangeBeginEnd) +{ + RangeStrongIndex begin(3); + RangeStrongIndex end(17); + auto r = RAJA::range(begin, end); + + static_assert( + std::is_same_v>, + "range(StrongIndex, StrongIndex) should deduce the strong storage type."); + ASSERT_EQ((RAJA::TypedRangeSegment(3, 17)), r); + ASSERT_EQ(RangeStrongIndex(3), *r.begin()); + ASSERT_EQ(RAJA::Index_type(14), r.size()); +} + +TEST(RangeSegmentUnitTest, MixedStrongRangeBeginEnd) +{ + static_assert(!RangeCallable); +} + +static_assert(RAJA::concepts::RangeConstructible); +static_assert(!RAJA::concepts::RangeConstructible); +static_assert(!RAJA::concepts::RangeConstructible); +static_assert(!RAJA::concepts::RangeConstructible); +static_assert( + TypedRangeCallable); +static_assert(!TypedRangeCallable); +static_assert(!TypedRangeCallable); +static_assert( + !TypedRangeCallable); + +TEST(RangeSegmentUnitTest, MixedStrongRangeStrongBegin) +{ + static_assert(!RangeCallable); +} + +static_assert(RAJA::concepts::RangeStrideConstructible); +static_assert(RAJA::concepts::RangeStrideConstructible); +static_assert(RAJA::concepts::RangeStrideConstructible); +static_assert(RAJA::concepts::RangeStrideConstructible); +static_assert(!RAJA::concepts::RangeStrideConstructible); +static_assert(!TypedRangeCallable); +static_assert(TypedRangeCallable); +static_assert(TypedRangeCallable); +static_assert(TypedRangeCallable); +static_assert(!TypedRangeCallable); +static_assert(TypedRangeCallable); +static_assert( + TypedRangeCallable); +static_assert( + !TypedRangeCallable); +static_assert( + !TypedRangeCallable); +static_assert(TypedRangeCallable); +static_assert(TypedRangeCallable); +static_assert(!RangeCallable); +static_assert( + RangeCallable); +static_assert(RangeCallable); + +TEST(RangeSegmentUnitTest, RangeBeginEndStridePositive) +{ + auto r = RAJA::range(2, 11, 3); + + ASSERT_EQ((RAJA::TypedRangeStrideSegment(2, 11, 3)), r); + ASSERT_EQ(3, r.size()); + ASSERT_EQ(2, *r.begin()); +} + +TEST(RangeSegmentUnitTest, MixedStrongRangeBeginEndStrideStrongEnd) +{ + auto r = RAJA::range(2, RangeStrongIndex(11), 3); + + static_assert( + std::is_same_v>, + "range(begin, StrongIndex, stride) should deduce the strong storage " + "type."); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(2, 11, 3)), r); + ASSERT_EQ(RAJA::Index_type(3), r.size()); + ASSERT_EQ(RangeStrongIndex(2), *r.begin()); +} + +TEST(RangeSegmentUnitTest, MixedStrongRangeBeginEndStrideStrongBegin) +{ + auto r = RAJA::range(RangeStrongIndex(2), 11, 3); + + static_assert( + std::is_same_v>, + "range(StrongIndex, end, stride) should deduce the strong storage " + "type."); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(2, 11, 3)), r); + ASSERT_EQ(RAJA::Index_type(3), r.size()); + ASSERT_EQ(RangeStrongIndex(2), *r.begin()); +} + +TEST(RangeSegmentUnitTest, RangeBeginEndStrongStride) +{ + static_assert(!RangeCallable); +} + +TEST(RangeSegmentUnitTest, ExplicitTypedRangeBeginEndStride) +{ + auto r_int = RAJA::range(3, 17, 1); + auto r_short = RAJA::range(3, 17, static_cast(1)); + auto r_long = RAJA::range(3, 17, static_cast(1)); + + static_assert( + std::is_same_v>, + "range(begin, end, int stride) should use int storage."); + static_assert( + std::is_same_v>, + "range(begin, end, short stride) should use int storage."); + static_assert( + std::is_same_v, + "range(begin, end, long stride) should use int storage."); + + ASSERT_EQ((RAJA::TypedRangeStrideSegment(3, 17, 1)), r_int); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(3, 17, 1)), r_short); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(3, 17, 1)), r_long); +} + +TEST(RangeSegmentUnitTest, ExplicitStrongRangeBeginEndStride) +{ + auto r_int = + RAJA::range(3, 17, 1); + auto r_strong_bounds = RAJA::range( + RangeStrongIndex(3), RangeStrongIndex(17), 1); + auto r_strong_stride = RAJA::range( + RangeStrongIndex(3), RangeStrongIndex(17), RangeStrongIndex(1)); + + static_assert( + std::is_same_v>, + "range(begin, end, stride) should use strong storage."); + static_assert( + std::is_same_v>, + "range(StrongIndex, StrongIndex, stride) should use strong " + "storage."); + static_assert( + std::is_same_v>, + "range(StrongIndex, StrongIndex, StrongIndex) should use " + "strong storage."); + + ASSERT_EQ((RAJA::TypedRangeStrideSegment(3, 17, 1)), + r_int); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(3, 17, 1)), + r_strong_bounds); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(3, 17, 1)), + r_strong_stride); +} + +TEST(RangeSegmentUnitTest, RangeBeginEndStrideNegative) +{ + auto r = RAJA::range(10, -1, -2); + + ASSERT_EQ((RAJA::TypedRangeStrideSegment(10, -1, -2)), r); + ASSERT_EQ(6, r.size()); + ASSERT_EQ(10, *r.begin()); +} + +TEST(RangeSegmentUnitTest, RangeBeginEndStrideDeducesFromStride) +{ + auto r = RAJA::range(2, 11, long {3}); + + static_assert( + std::is_same_v>, + "range(begin, end, stride) should include stride in the storage type."); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(2, 11, 3)), r); + ASSERT_EQ(3, r.size()); + ASSERT_EQ(2, *r.begin()); +} + +TEST(RangeSegmentUnitTest, RangeBeginEndStrideDeducesCommonType) +{ + auto r = RAJA::range(3, 17, static_cast(1)); + + static_assert( + std::is_same_v>, + "range(begin, end, stride) should use the common storage type."); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(3, 17, 1)), r); + ASSERT_EQ(14, r.size()); + ASSERT_EQ(3, *r.begin()); +} + +TEST(RangeSegmentUnitTest, RangeBeginEndUnsignedStrideWorks) +{ + auto r = RAJA::range(-2, 11, 3u); + + static_assert( + std::is_same_v>, + "range(begin, end, unsigned stride) should preserve signed storage when " + "begin/end are signed."); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(-2, 11, 3)), r); + ASSERT_EQ(5, r.size()); + ASSERT_EQ(-2, *r.begin()); +} + +TEST(RangeSegmentUnitTest, MakeStridedRangeDeducesFromStride) +{ + auto r = RAJA::make_strided_range(2, 11, long {3}); + + static_assert( + std::is_same_v>, + "make_strided_range(begin, end, stride) should include stride in the " + "storage type."); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(2, 11, 3)), r); + ASSERT_EQ(3, r.size()); + ASSERT_EQ(2, *r.begin()); +} + +TEST(RangeSegmentUnitTest, MakeStridedRangeUnsignedStrideWorks) +{ + auto r = RAJA::make_strided_range(-2, 11, 3u); + + static_assert( + std::is_same_v>, + "make_strided_range(begin, end, unsigned stride) should preserve signed " + "storage when begin/end are signed."); + ASSERT_EQ((RAJA::TypedRangeStrideSegment(-2, 11, 3)), r); + ASSERT_EQ(5, r.size()); + ASSERT_EQ(-2, *r.begin()); +}