mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-30 02:31:44 +00:00
[wpiutil] Add rotated_span (#7111)
This commit is contained in:
254
wpiutil/src/main/native/include/wpi/rotated_span.h
Normal file
254
wpiutil/src/main/native/include/wpi/rotated_span.h
Normal file
@@ -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 <array>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
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 <typename T, size_t Extent = std::dynamic_extent>
|
||||
class rotated_span {
|
||||
public:
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<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<T, Extent> data, // NOLINT
|
||||
int rotation = 0)
|
||||
: m_data{data}, m_offset{MakeOffset(data.size(), rotation)} {}
|
||||
|
||||
template <std::contiguous_iterator It>
|
||||
constexpr explicit(extent != std::dynamic_extent)
|
||||
rotated_span(It first, size_type count, int rotation = 0) noexcept
|
||||
: rotated_span{std::span<T, Extent>{first, count}, rotation} {}
|
||||
|
||||
template <std::contiguous_iterator It, std::sized_sentinel_for<It> End>
|
||||
requires(!std::is_convertible_v<End, size_type>)
|
||||
constexpr explicit(extent != std::dynamic_extent)
|
||||
rotated_span(It first, End last,
|
||||
int rotation = 0) noexcept(noexcept(std::span<T, Extent>{
|
||||
first, last}))
|
||||
: rotated_span{std::span<T, Extent>{first, last}, rotation} {}
|
||||
|
||||
template <size_t ArrayExtent>
|
||||
requires(Extent == std::dynamic_extent || ArrayExtent == Extent)
|
||||
constexpr rotated_span( // NOLINT
|
||||
std::type_identity_t<element_type> (&arr)[ArrayExtent],
|
||||
int rotation = 0) noexcept
|
||||
: rotated_span{std::span<T, Extent>{arr}, rotation} {}
|
||||
|
||||
template <typename Tp, size_t ArrayExtent>
|
||||
constexpr rotated_span(std::array<Tp, ArrayExtent>& arr, // NOLINT
|
||||
int rotation = 0) noexcept
|
||||
: rotated_span{std::span<T, Extent>{arr}, rotation} {}
|
||||
|
||||
template <typename Tp, size_t ArrayExtent>
|
||||
constexpr rotated_span(const std::array<Tp, ArrayExtent>& arr, // NOLINT
|
||||
int rotation = 0) noexcept
|
||||
: rotated_span{std::span<T, Extent>{arr}, rotation} {}
|
||||
|
||||
template <typename OType, size_t OExtent>
|
||||
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<OType, OExtent>& other,
|
||||
int rotation) noexcept
|
||||
: m_data{other.m_data},
|
||||
m_offset{MakeOffset(other.m_data.size(), other.m_offset + rotation)} {}
|
||||
|
||||
template <typename OType, size_t OExtent>
|
||||
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<OType, OExtent>& 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<T, Extent> 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<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<T, Extent> 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 <typename Type, size_t ArrayExtent>
|
||||
rotated_span(Type (&)[ArrayExtent]) -> rotated_span<Type, ArrayExtent>;
|
||||
|
||||
template <typename Type, size_t ArrayExtent>
|
||||
rotated_span(std::array<Type, ArrayExtent>&) -> rotated_span<Type, ArrayExtent>;
|
||||
|
||||
template <std::contiguous_iterator It, typename End>
|
||||
rotated_span(It, End)
|
||||
-> rotated_span<std::remove_reference_t<std::iter_reference_t<It>>>;
|
||||
|
||||
template <typename Type, size_t Extent>
|
||||
[[nodiscard]]
|
||||
inline rotated_span<const std::byte, Extent == std::dynamic_extent
|
||||
? std::dynamic_extent
|
||||
: Extent * sizeof(Type)>
|
||||
as_bytes(rotated_span<Type, Extent> sp) noexcept {
|
||||
return {std::as_bytes(sp.data()), sp.offset() * sizeof(Type)};
|
||||
}
|
||||
|
||||
template <typename Type, size_t Extent>
|
||||
requires(!std::is_const_v<Type>)
|
||||
[[nodiscard]]
|
||||
inline rotated_span<std::byte, Extent == std::dynamic_extent
|
||||
? std::dynamic_extent
|
||||
: Extent * sizeof(Type)>
|
||||
as_writable_bytes(rotated_span<Type, Extent> sp) noexcept {
|
||||
return {std::as_writable_bytes(sp.data()), sp.offset() * sizeof(Type)};
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
204
wpiutil/src/test/native/cpp/rotated_span_test.cpp
Normal file
204
wpiutil/src/test/native/cpp/rotated_span_test.cpp
Normal file
@@ -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 <array>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// constexpr
|
||||
static constexpr std::array<int, 10> 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<int, 10> 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<int> cvec_values{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
|
||||
// non-const
|
||||
static std::array<int, 10> 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<int> vec_values{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
|
||||
TEST(CircularSpanTest, Constexpr) {
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cesarr_values};
|
||||
static_assert(sp[5] == cesarr_values[5]);
|
||||
}
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cearr_values};
|
||||
static_assert(sp[5] == cearr_values[5]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CircularSpanTest, ConstructConst) {
|
||||
{
|
||||
wpi::rotated_span<const int, 10> sp{csarr_values};
|
||||
EXPECT_EQ(sp[5], sarr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<const int> sp{csarr_values};
|
||||
EXPECT_EQ(sp[5], sarr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<const int, 10> sp{carr_values};
|
||||
EXPECT_EQ(sp[5], arr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<const int> sp{carr_values};
|
||||
EXPECT_EQ(sp[5], arr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<const int> sp{cvec_values.begin(), cvec_values.end()};
|
||||
EXPECT_EQ(sp[5], vec_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<const int> sp{cvec_values.data(), cvec_values.size()};
|
||||
EXPECT_EQ(sp[5], vec_values[5]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CircularSpanTest, ConstructNonConst) {
|
||||
{
|
||||
wpi::rotated_span<int, 10> sp{sarr_values};
|
||||
EXPECT_EQ(sp[5], sarr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<int> sp{sarr_values};
|
||||
EXPECT_EQ(sp[5], sarr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<int, 10> sp{arr_values};
|
||||
EXPECT_EQ(sp[5], arr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<int> sp{arr_values};
|
||||
EXPECT_EQ(sp[5], arr_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<int> sp{vec_values.begin(), vec_values.end()};
|
||||
EXPECT_EQ(sp[5], vec_values[5]);
|
||||
}
|
||||
{
|
||||
wpi::rotated_span<int> sp{vec_values.data(), vec_values.size()};
|
||||
EXPECT_EQ(sp[5], vec_values[5]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CircularSpanTest, ConstructRotated) {
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cesarr_values, 1};
|
||||
static_assert(sp[5] == cesarr_values[6]);
|
||||
}
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cesarr_values, 9};
|
||||
static_assert(sp[5] == cesarr_values[4]);
|
||||
}
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cesarr_values, 10};
|
||||
static_assert(sp[5] == cesarr_values[5]);
|
||||
}
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cesarr_values, 11};
|
||||
static_assert(sp[5] == cesarr_values[6]);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cearr_values, -1};
|
||||
static_assert(sp[5] == cearr_values[4]);
|
||||
}
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cearr_values, -9};
|
||||
static_assert(sp[5] == cearr_values[6]);
|
||||
}
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cearr_values, -10};
|
||||
static_assert(sp[5] == cearr_values[5]);
|
||||
}
|
||||
{
|
||||
constexpr wpi::rotated_span<const int, 10> sp{cearr_values, -11};
|
||||
static_assert(sp[5] == cearr_values[4]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CircularSpanTest, Rotate) {
|
||||
constexpr wpi::rotated_span<const int, 10> 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<int>) {}
|
||||
void const_unsized_func(wpi::rotated_span<const int>) {}
|
||||
void sized_func(wpi::rotated_span<int, 10>) {}
|
||||
void const_sized_func(wpi::rotated_span<const int, 10>) {}
|
||||
|
||||
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<const int> 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<int> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user