diff --git a/wpiutil/src/main/native/include/wpi/rotated_span.h b/wpiutil/src/main/native/include/wpi/rotated_span.h new file mode 100644 index 0000000000..92d7036363 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/rotated_span.h @@ -0,0 +1,254 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include +#include + +namespace wpi { + +/** + * This is a simple rotated span view. Indexed/iterated access provides a + * continuous view of the underlying span that wraps at the span size. An + * internal offset determines the starting location. + * + * Constructors take a "rotation" value--if positive, the offset is the same as + * the rotation; if negative, the offset is set relative to the end of the + * array. + * + * For example, given an array of 5 values, providing a rotation value of 2 + * will result in an index of 0 accessing underlying span index 2, index 2 + * accessing underlying span index 4, and index 4 accessing underlying span + * index 1. + * + * Similarly, providing a rotation value of -2 will result in index 0 accessing + * underlying span index 3 (5-2), index 2 accessing underlying span index 0, + * and index 4 accessing underlying span index 2. + * + * @tparam T element type + * @tparam Extent static sized extent, or std::dynamic_extent + */ +template +class rotated_span { + public: + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = element_type&; + using const_reference = const element_type&; + + // member constants + static constexpr size_t extent = Extent; + + // constructors, copy and assignment + + constexpr rotated_span() noexcept + requires(Extent == std::dynamic_extent || Extent == 0) + = default; + + constexpr /*implicit*/ rotated_span(std::span data, // NOLINT + int rotation = 0) + : m_data{data}, m_offset{MakeOffset(data.size(), rotation)} {} + + template + constexpr explicit(extent != std::dynamic_extent) + rotated_span(It first, size_type count, int rotation = 0) noexcept + : rotated_span{std::span{first, count}, rotation} {} + + template End> + requires(!std::is_convertible_v) + constexpr explicit(extent != std::dynamic_extent) + rotated_span(It first, End last, + int rotation = 0) noexcept(noexcept(std::span{ + first, last})) + : rotated_span{std::span{first, last}, rotation} {} + + template + requires(Extent == std::dynamic_extent || ArrayExtent == Extent) + constexpr rotated_span( // NOLINT + std::type_identity_t (&arr)[ArrayExtent], + int rotation = 0) noexcept + : rotated_span{std::span{arr}, rotation} {} + + template + constexpr rotated_span(std::array& arr, // NOLINT + int rotation = 0) noexcept + : rotated_span{std::span{arr}, rotation} {} + + template + constexpr rotated_span(const std::array& arr, // NOLINT + int rotation = 0) noexcept + : rotated_span{std::span{arr}, rotation} {} + + template + requires(Extent == std::dynamic_extent || OExtent == std::dynamic_extent || + Extent == OExtent) + constexpr explicit(extent != std::dynamic_extent && + OExtent == std::dynamic_extent) + rotated_span(const rotated_span& other, + int rotation) noexcept + : m_data{other.m_data}, + m_offset{MakeOffset(other.m_data.size(), other.m_offset + rotation)} {} + + template + requires(Extent == std::dynamic_extent || OExtent == std::dynamic_extent || + Extent == OExtent) + constexpr explicit(extent != std::dynamic_extent && + OExtent == std::dynamic_extent) rotated_span( // NOLINT + const rotated_span& other) noexcept + : m_data{other.m_data}, m_offset{other.m_offset} {} + constexpr rotated_span& operator=(const rotated_span&) noexcept = default; + constexpr rotated_span(rotated_span&&) noexcept = default; + constexpr rotated_span& operator=(rotated_span&&) noexcept = default; + + ~rotated_span() noexcept = default; + + // observers + + constexpr std::span data() const noexcept { return m_data; } + + constexpr size_type offset() const noexcept { return m_offset; } + + constexpr size_type size() const noexcept { return m_data.size(); } + + constexpr size_type size_bytes() const noexcept { + return m_data.size_bytes(); + } + + constexpr bool empty() const noexcept { return m_data.empty(); } + + // element access + + [[nodiscard]] + constexpr reference front() const noexcept { + return m_data[m_offset]; + } + + [[nodiscard]] + constexpr reference back() const noexcept { + return m_data[(m_offset + m_data.size() - 1) % m_data.size()]; + } + + [[nodiscard]] + constexpr reference operator[](size_type idx) const noexcept { + return m_data[(m_offset + idx) % m_data.size()]; + } + + // iterator support + + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = rotated_span::value_type; + using difference_type = rotated_span::difference_type; + using pointer = rotated_span::pointer; + using reference = rotated_span::reference; + + constexpr iterator(const rotated_span* obj, size_type idx) noexcept + : m_obj{obj}, m_idx{idx} {} + + constexpr iterator& operator++() noexcept { + ++m_idx; + return *this; + } + constexpr iterator operator++(int) noexcept { + iterator retval = *this; + ++(*this); + return retval; + } + constexpr bool operator==(const iterator&) const = default; + constexpr reference operator*() { return (*m_obj)[m_idx]; } + + private: + const rotated_span* m_obj; + size_type m_idx; + }; + + using reverse_iterator = std::reverse_iterator; + + [[nodiscard]] + constexpr iterator begin() const noexcept { + return iterator(this, 0); + } + + [[nodiscard]] + constexpr iterator end() const noexcept { + return iterator(this, size()); + } + + [[nodiscard]] + constexpr reverse_iterator rbegin() const noexcept { + return reverse_iterator(end()); + } + + [[nodiscard]] + constexpr reverse_iterator rend() const noexcept { + return reverse_iterator(begin()); + } + + // subviews + [[nodiscard]] + constexpr rotated_span rotate(int amt) const noexcept { + return rotated_span{*this, amt}; + } + + private: + std::span m_data; + size_type m_offset; + + static constexpr size_type MakeOffset(size_t size, int rotation) { + if (size == 0) { + return 0; + } + if (rotation >= 0) { + return rotation % size; + } else { + // The usual arithmetic conversions mean that rotation is converted to + // size_t, which is unsigned. Converting negative values to unsigned + // integer types produces large numbers with useless remainders, so + // instead make rotation positive before doing the modulo arithmetic. + return size - (-rotation % size); + } + } +}; + +// deduction guides + +template +rotated_span(Type (&)[ArrayExtent]) -> rotated_span; + +template +rotated_span(std::array&) -> rotated_span; + +template +rotated_span(It, End) + -> rotated_span>>; + +template +[[nodiscard]] +inline rotated_span +as_bytes(rotated_span sp) noexcept { + return {std::as_bytes(sp.data()), sp.offset() * sizeof(Type)}; +} + +template + requires(!std::is_const_v) +[[nodiscard]] +inline rotated_span +as_writable_bytes(rotated_span sp) noexcept { + return {std::as_writable_bytes(sp.data()), sp.offset() * sizeof(Type)}; +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/rotated_span_test.cpp b/wpiutil/src/test/native/cpp/rotated_span_test.cpp new file mode 100644 index 0000000000..e2596bad6e --- /dev/null +++ b/wpiutil/src/test/native/cpp/rotated_span_test.cpp @@ -0,0 +1,204 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/rotated_span.h" // NOLINT(build/include_order) + +#include +#include + +#include + +// constexpr +static constexpr std::array cesarr_values = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; +static constexpr int cearr_values[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + +// const +static const std::array csarr_values = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; +static const int carr_values[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; +static const std::vector cvec_values{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + +// non-const +static std::array sarr_values = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; +static int arr_values[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; +static std::vector vec_values{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + +TEST(CircularSpanTest, Constexpr) { + { + constexpr wpi::rotated_span sp{cesarr_values}; + static_assert(sp[5] == cesarr_values[5]); + } + { + constexpr wpi::rotated_span sp{cearr_values}; + static_assert(sp[5] == cearr_values[5]); + } +} + +TEST(CircularSpanTest, ConstructConst) { + { + wpi::rotated_span sp{csarr_values}; + EXPECT_EQ(sp[5], sarr_values[5]); + } + { + wpi::rotated_span sp{csarr_values}; + EXPECT_EQ(sp[5], sarr_values[5]); + } + { + wpi::rotated_span sp{carr_values}; + EXPECT_EQ(sp[5], arr_values[5]); + } + { + wpi::rotated_span sp{carr_values}; + EXPECT_EQ(sp[5], arr_values[5]); + } + { + wpi::rotated_span sp{cvec_values.begin(), cvec_values.end()}; + EXPECT_EQ(sp[5], vec_values[5]); + } + { + wpi::rotated_span sp{cvec_values.data(), cvec_values.size()}; + EXPECT_EQ(sp[5], vec_values[5]); + } +} + +TEST(CircularSpanTest, ConstructNonConst) { + { + wpi::rotated_span sp{sarr_values}; + EXPECT_EQ(sp[5], sarr_values[5]); + } + { + wpi::rotated_span sp{sarr_values}; + EXPECT_EQ(sp[5], sarr_values[5]); + } + { + wpi::rotated_span sp{arr_values}; + EXPECT_EQ(sp[5], arr_values[5]); + } + { + wpi::rotated_span sp{arr_values}; + EXPECT_EQ(sp[5], arr_values[5]); + } + { + wpi::rotated_span sp{vec_values.begin(), vec_values.end()}; + EXPECT_EQ(sp[5], vec_values[5]); + } + { + wpi::rotated_span sp{vec_values.data(), vec_values.size()}; + EXPECT_EQ(sp[5], vec_values[5]); + } +} + +TEST(CircularSpanTest, ConstructRotated) { + { + constexpr wpi::rotated_span sp{cesarr_values, 1}; + static_assert(sp[5] == cesarr_values[6]); + } + { + constexpr wpi::rotated_span sp{cesarr_values, 9}; + static_assert(sp[5] == cesarr_values[4]); + } + { + constexpr wpi::rotated_span sp{cesarr_values, 10}; + static_assert(sp[5] == cesarr_values[5]); + } + { + constexpr wpi::rotated_span sp{cesarr_values, 11}; + static_assert(sp[5] == cesarr_values[6]); + } + + { + constexpr wpi::rotated_span sp{cearr_values, -1}; + static_assert(sp[5] == cearr_values[4]); + } + { + constexpr wpi::rotated_span sp{cearr_values, -9}; + static_assert(sp[5] == cearr_values[6]); + } + { + constexpr wpi::rotated_span sp{cearr_values, -10}; + static_assert(sp[5] == cearr_values[5]); + } + { + constexpr wpi::rotated_span sp{cearr_values, -11}; + static_assert(sp[5] == cearr_values[4]); + } +} + +TEST(CircularSpanTest, Rotate) { + constexpr wpi::rotated_span sp{cesarr_values, 1}; + static_assert(sp[5] == cesarr_values[6]); + static_assert(sp.rotate(2)[5] == cesarr_values[8]); + static_assert(sp.rotate(9)[5] == cesarr_values[5]); + static_assert(sp.rotate(10)[5] == cesarr_values[6]); + static_assert(sp.rotate(11)[5] == cesarr_values[7]); + static_assert(sp.rotate(-1)[5] == cesarr_values[5]); + static_assert(sp.rotate(-2)[5] == cesarr_values[4]); + static_assert(sp.rotate(-9)[5] == cesarr_values[7]); + static_assert(sp.rotate(-10)[5] == cesarr_values[6]); + static_assert(sp.rotate(-11)[5] == cesarr_values[5]); +} + +void unsized_func(wpi::rotated_span) {} +void const_unsized_func(wpi::rotated_span) {} +void sized_func(wpi::rotated_span) {} +void const_sized_func(wpi::rotated_span) {} + +TEST(CircularSpanTest, Implicit) { + // unsized_func(csarr_values); // error + const_unsized_func(csarr_values); + // sized_func(csarr_values); // error + const_sized_func(sarr_values); + + // unsized_func(carr_values); // error + const_unsized_func(carr_values); + // sized_func(carr_values); // error + const_sized_func(carr_values); + + unsized_func(sarr_values); + const_unsized_func(sarr_values); + sized_func(sarr_values); + const_sized_func(sarr_values); + + unsized_func(arr_values); + const_unsized_func(arr_values); + sized_func(arr_values); + const_sized_func(arr_values); +} + +TEST(CircularSpanTest, IteratorConst) { + wpi::rotated_span sp_sarr{csarr_values}; + + // iterator + int i = 0; + for (auto& elem : sp_sarr) { + EXPECT_EQ(sarr_values[i], elem); + ++i; + } + + // const_iterator + i = 0; + for (const auto& elem : sp_sarr) { + EXPECT_EQ(sarr_values[i], elem); + ++i; + } +} + +TEST(CircularSpanTest, IteratorNonConst) { + wpi::rotated_span sp_sarr{sarr_values}; + + // iterator + int i = 0; + for (auto& elem : sp_sarr) { + EXPECT_EQ(sarr_values[i], elem); + ++i; + } + + // const_iterator + i = 0; + for (const auto& elem : sp_sarr) { + EXPECT_EQ(sarr_values[i], elem); + ++i; + } +}