Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions include/beman/span/span.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,32 @@ template <class Range, class ElementType>
inline constexpr bool is_compatible_element_type_v =
std::is_convertible_v<std::remove_reference_t<std::ranges::range_reference_t<Range>> (*)[], ElementType (*)[]>;

template <std::size_t Extent>
struct compressed_size {
constexpr compressed_size() = default;
constexpr explicit compressed_size(std::size_t) {}

constexpr std::size_t size() const { return Extent; }
constexpr void reduce_size() {}
};

template <>
struct compressed_size<dynamic_extent> {
constexpr explicit compressed_size(std::size_t sz) : size_(sz) {}
constexpr std::size_t size() const { return size_; }
constexpr void reduce_size(std::size_t N) { size_ -= N; }

private:
std::size_t size_;
};

} // namespace detail

// 26.7.3 Class template span [views.span]
template <class ElementType, std::size_t Extent>
class span {
class span : public detail::compressed_size<Extent> {
using size_holder = detail::compressed_size<Extent>;

public:
// Member types
using element_type = ElementType;
Expand All @@ -83,18 +104,18 @@ class span {

// Default constructor: only valid when Extent == 0 or Extent == dynamic_extent
template <std::size_t E = Extent, std::enable_if_t<E == dynamic_extent || E == 0, int> = 0>
constexpr span() noexcept : data_(nullptr), size_(0) {}
constexpr span() noexcept : data_(nullptr), size_holder(0) {}

// Pointer + count constructor.
constexpr explicit(Extent != dynamic_extent) span(pointer ptr, size_type count) : data_(ptr), size_(count) {
constexpr explicit(Extent != dynamic_extent) span(pointer ptr, size_type count) : data_(ptr), size_holder(count) {
if constexpr (Extent != dynamic_extent) {
assert(count == Extent);
}
}

// Pointer pair constructor.
constexpr explicit(Extent != dynamic_extent) span(pointer first, pointer last)
: data_(first), size_(static_cast<size_type>(last - first)) {
: data_(first), size_holder(static_cast<size_type>(last - first)) {
if constexpr (Extent != dynamic_extent) {
assert(static_cast<size_type>(last - first) == Extent);
}
Expand All @@ -109,21 +130,21 @@ class span {
ElementType (*)[]>,
int> = 0>
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
constexpr span(ElementType (&arr)[N]) noexcept : data_(arr), size_(N) {}
constexpr span(ElementType (&arr)[N]) noexcept : data_(arr), size_holder(N) {}

// std::array constructor (fixed-size)
template <class T,
std::size_t N,
std::enable_if_t<Extent == dynamic_extent || Extent == N, int> = 0,
std::enable_if_t<std::is_convertible_v<T (*)[], ElementType (*)[]>, int> = 0>
constexpr span(std::array<T, N>& arr) noexcept : data_(arr.data()), size_(N) {}
constexpr span(std::array<T, N>& arr) noexcept : data_(arr.data()), size_holder(N) {}

// const std::array constructor
template <class T,
std::size_t N,
std::enable_if_t<Extent == dynamic_extent || Extent == N, int> = 0,
std::enable_if_t<std::is_convertible_v<const T (*)[], ElementType (*)[]>, int> = 0>
constexpr span(const std::array<T, N>& arr) noexcept : data_(arr.data()), size_(N) {}
constexpr span(const std::array<T, N>& arr) noexcept : data_(arr.data()), size_holder(N) {}

// Range constructor (generic contiguous range)
template <class Range,
Expand All @@ -135,7 +156,7 @@ class span {
std::enable_if_t<std::ranges::borrowed_range<Range> || std::is_const_v<ElementType>, int> = 0,
std::enable_if_t<detail::is_compatible_element_type_v<Range, ElementType>, int> = 0>
constexpr explicit(Extent != dynamic_extent) span(Range&& r)
: data_(std::ranges::data(r)), size_(std::ranges::size(r)) {
: data_(std::ranges::data(r)), size_holder(std::ranges::size(r)) {
if constexpr (Extent != dynamic_extent) {
if constexpr (requires { std::integral_constant<size_type, std::ranges::size(r)>{}; }) {
static_assert(std::ranges::size(r) == Extent,
Expand All @@ -156,7 +177,7 @@ class span {
std::enable_if_t<std::is_const_v<T_>, int> = 0,
std::enable_if_t<std::is_same_v<InitListValueType, std::remove_cv_t<T_>>, int> = 0>
constexpr explicit(Extent != dynamic_extent) span(std::initializer_list<InitListValueType> il)
: data_(il.begin()), size_(il.size()) {
: data_(il.begin()), size_holder(il.size()) {
if constexpr (Extent != dynamic_extent) {
assert(il.size() == Extent);
}
Expand All @@ -170,7 +191,7 @@ class span {
int> = 0>
constexpr explicit(Extent != dynamic_extent && OtherExtent == dynamic_extent)
span(const span<OtherElementType, OtherExtent>& s) noexcept
: data_(s.data()), size_(s.size()) {
: data_(s.data()), size_holder(s.size()) {
if constexpr (Extent != dynamic_extent) {
assert(s.size() == Extent);
}
Expand Down Expand Up @@ -237,19 +258,19 @@ class span {
{
assert(n <= size());
data_ += n;
size_ -= n;
size_holder::reduce_size(n);
}

constexpr void remove_suffix(size_type n) noexcept
requires(Extent == dynamic_extent)
{
assert(n <= size());
size_ -= n;
size_holder::reduce_size(n);
}

// 26.7.3.4 Observers [span.obs]

[[nodiscard]] constexpr size_type size() const noexcept { return size_; }
[[nodiscard]] constexpr size_type size() const noexcept { return size_holder::size(); }

[[nodiscard]] constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); }

Expand Down Expand Up @@ -288,20 +309,19 @@ class span {
// 26.7.3.6 Iterator support [span.iterators]

constexpr iterator begin() const noexcept { return data_; }
constexpr iterator end() const noexcept { return data_ + size_; }
constexpr iterator end() const noexcept { return data_ + size(); }

constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); }
constexpr reverse_iterator rend() const noexcept { return reverse_iterator(begin()); }

// cbegin/cend yield iterators to const elements even when ElementType is non-const
constexpr const_iterator cbegin() const noexcept { return data_; }
constexpr const_iterator cend() const noexcept { return data_ + size_; }
constexpr const_iterator cend() const noexcept { return data_ + size(); }
constexpr const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); }
constexpr const_reverse_iterator crend() const noexcept { return const_reverse_iterator(cbegin()); }

private:
pointer data_;
size_type size_;
pointer data_;
};

// Deduction guides (C++17)
Expand Down
31 changes: 30 additions & 1 deletion tests/beman/span/span.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ TEST(SpanTest, static_extent_constant) {

TEST(SpanConstruction, default_dynamic) {
bsp::span<int> s;
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 0u);
EXPECT_EQ(s.data(), nullptr);
EXPECT_TRUE(s.empty());
}

TEST(SpanConstruction, default_static_zero) {
bsp::span<int, 0> s;
static_assert(sizeof(s) == sizeof(int*));
EXPECT_EQ(s.size(), 0u);
EXPECT_TRUE(s.empty());
}
Expand All @@ -51,6 +53,7 @@ TEST(SpanConstruction, default_static_zero) {
TEST(SpanConstruction, pointer_and_count_dynamic) {
int arr[] = {1, 2, 3, 4, 5};
bsp::span<int> s(arr, 5);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 5u);
EXPECT_EQ(s.data(), arr);
EXPECT_FALSE(s.empty());
Expand All @@ -59,6 +62,7 @@ TEST(SpanConstruction, pointer_and_count_dynamic) {
TEST(SpanConstruction, pointer_and_count_static) {
int arr[] = {10, 20, 30};
bsp::span<int, 3> s(arr, 3);
static_assert(sizeof(s) == sizeof(int*));
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s.data(), arr);
}
Expand All @@ -70,6 +74,7 @@ TEST(SpanConstruction, pointer_and_count_static) {
TEST(SpanConstruction, pointer_pair) {
int arr[] = {5, 6, 7, 8};
bsp::span<int> s(arr, arr + 4);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 4u);
EXPECT_EQ(s[0], 5);
EXPECT_EQ(s[3], 8);
Expand All @@ -82,19 +87,22 @@ TEST(SpanConstruction, pointer_pair) {
TEST(SpanConstruction, c_array_dynamic) {
int arr[] = {1, 2, 3};
bsp::span<int> s(arr);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s.data(), arr);
}

TEST(SpanConstruction, c_array_static) {
int arr[] = {10, 20};
bsp::span<int, 2> s(arr);
static_assert(sizeof(s) == sizeof(int*));
EXPECT_EQ(s.size(), 2u);
}

TEST(SpanConstruction, c_array_deduction) {
int arr[] = {1, 2, 3, 4};
bsp::span s(arr);
static_assert(sizeof(s) == sizeof(int*));
static_assert(std::is_same_v<decltype(s), bsp::span<int, 4>>);
EXPECT_EQ(s.size(), 4u);
}
Expand All @@ -106,13 +114,15 @@ TEST(SpanConstruction, c_array_deduction) {
TEST(SpanConstruction, std_array_mutable) {
std::array<int, 4> arr = {1, 2, 3, 4};
bsp::span<int> s(arr);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 4u);
EXPECT_EQ(s.data(), arr.data());
}

TEST(SpanConstruction, std_array_const) {
const std::array<int, 3> arr = {7, 8, 9};
bsp::span<const int> s(arr);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s[0], 7);
}
Expand All @@ -121,12 +131,14 @@ TEST(SpanConstruction, std_array_deduction_mutable) {
std::array<double, 2> arr = {1.0, 2.0};
bsp::span s(arr);
static_assert(std::is_same_v<decltype(s), bsp::span<double, 2>>);
static_assert(sizeof(s) == sizeof(int*));
}

TEST(SpanConstruction, std_array_deduction_const) {
const std::array<double, 2> arr = {3.0, 4.0};
bsp::span s(arr);
static_assert(std::is_same_v<decltype(s), bsp::span<const double, 2>>);
static_assert(sizeof(s) == sizeof(int*));
}

// ---------------------------------------------------------------------------
Expand All @@ -136,26 +148,30 @@ TEST(SpanConstruction, std_array_deduction_const) {
TEST(SpanConstruction, from_vector) {
std::vector<int> v = {1, 2, 3, 4, 5};
bsp::span<int> s(v);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 5u);
EXPECT_EQ(s.data(), v.data());
}

TEST(SpanConstruction, from_const_vector) {
const std::vector<int> v = {10, 20};
bsp::span<const int> s(v);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 2u);
EXPECT_EQ(s[1], 20);
}

TEST(SpanConstruction, lwg4397_constant_size_matches_extent) {
bsp::span<int, 0> s(std::views::empty<int>);
static_assert(sizeof(s) == sizeof(int*));
EXPECT_EQ(s.size(), 0u);
EXPECT_TRUE(s.empty());
}

TEST(SpanConstruction, lwg4397_runtime_sized_range_not_rejected) {
std::vector<int> v(3);
bsp::span<int, 3> s(v);
static_assert(sizeof(s) == sizeof(int*));
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s.data(), v.data());
}
Expand All @@ -168,6 +184,8 @@ TEST(SpanConstruction, copy_dynamic_from_dynamic) {
int arr[] = {1, 2, 3};
bsp::span<int> a(arr);
bsp::span<int> b(a);
static_assert(sizeof(a) == sizeof(int*) + sizeof(std::size_t));
static_assert(sizeof(b) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(b.size(), 3u);
EXPECT_EQ(b.data(), arr);
}
Expand All @@ -176,6 +194,8 @@ TEST(SpanConstruction, const_from_mutable) {
int arr[] = {4, 5, 6};
bsp::span<int> mutable_s(arr);
bsp::span<const int> const_s(mutable_s);
static_assert(sizeof(mutable_s) == sizeof(int*) + sizeof(std::size_t));
static_assert(sizeof(const_s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(const_s.size(), 3u);
EXPECT_EQ(const_s[0], 4);
}
Expand All @@ -184,11 +204,14 @@ TEST(SpanConstruction, dynamic_from_static) {
int arr[] = {1, 2, 3, 4};
bsp::span<int, 4> fixed(arr);
bsp::span<int> dynamic(fixed);
static_assert(sizeof(fixed) == sizeof(int*));
static_assert(sizeof(dynamic) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(dynamic.size(), 4u);
}

TEST(SpanInitList, dynamic_from_braced_list) {
auto verify = [](bsp::span<const int> s) {
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s[0], 1);
EXPECT_EQ(s[1], 2);
Expand All @@ -199,6 +222,7 @@ TEST(SpanInitList, dynamic_from_braced_list) {

TEST(SpanInitList, fixed_extent_from_braced_list) {
auto verify = [](bsp::span<const int, 3> s) {
static_assert(sizeof(s) == sizeof(int*));
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s[0], 1);
EXPECT_EQ(s[2], 3);
Expand All @@ -208,6 +232,7 @@ TEST(SpanInitList, fixed_extent_from_braced_list) {

TEST(SpanInitList, const_bool_from_braced_list) {
auto verify = [](bsp::span<const bool> s) {
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 3u);
EXPECT_TRUE(s[0]);
EXPECT_FALSE(s[1]);
Expand All @@ -219,6 +244,7 @@ TEST(SpanInitList, const_bool_from_braced_list) {
TEST(SpanInitList, named_initializer_list_keeps_array_alive) {
std::initializer_list<int> il = {10, 20, 30};
bsp::span<const int> s(il);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s[0], 10);
EXPECT_EQ(s[2], 30);
Expand All @@ -229,6 +255,7 @@ TEST(SpanInitList, pointer_and_size_resolves_to_pointer_count_ctor) {
bool* ptr = data;
std::size_t n = 4;
bsp::span<const bool> s(ptr, n);
static_assert(sizeof(s) == sizeof(int*) + sizeof(std::size_t));
EXPECT_EQ(s.size(), 4u);
EXPECT_EQ(s.data(), data);
}
Expand Down Expand Up @@ -606,7 +633,8 @@ TEST(SpanObjectRepresentation, as_writable_bytes) {
TEST(SpanObjectRepresentation, as_bytes_fixed_extent) {
int arr[3] = {1, 2, 3};
bsp::span<int, 3> s(arr);
auto bytes = bsp::as_bytes(s);
static_assert(sizeof(s) == sizeof(int*));
auto bytes = bsp::as_bytes(s);
static_assert(decltype(bytes)::extent == 3 * sizeof(int));
EXPECT_EQ(bytes.size(), 3 * sizeof(int));
}
Expand All @@ -621,6 +649,7 @@ static constexpr int kConstexprArr[] = {1, 2, 3};

TEST(SpanConstexpr, size_and_access) {
constexpr bsp::span<const int, 3> s(kConstexprArr);
static_assert(sizeof(s) == sizeof(int*));
static_assert(s.size() == 3);
static_assert(s[0] == 1);
static_assert(s.front() == 1);
Expand Down