Skip to content
Open
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
46 changes: 37 additions & 9 deletions include/beman/span/span.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <limits>
#include <ranges>
#include <stdexcept>
#include <tuple>
#include <type_traits>

namespace beman::span {
Expand Down Expand Up @@ -104,18 +105,18 @@ class span : public detail::compressed_size<Extent> {

// 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_holder(0) {}
constexpr span() noexcept : size_holder(0), data_(nullptr) {}

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

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

// 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_holder(N) {}
constexpr span(std::array<T, N>& arr) noexcept : size_holder(N), data_(arr.data()) {}

// 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_holder(N) {}
constexpr span(const std::array<T, N>& arr) noexcept : size_holder(N), data_(arr.data()) {}

// Range constructor (generic contiguous range)
template <class Range,
Expand All @@ -156,7 +157,7 @@ class span : public detail::compressed_size<Extent> {
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_holder(std::ranges::size(r)) {
: size_holder(std::ranges::size(r)), data_(std::ranges::data(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 @@ -177,7 +178,7 @@ class span : public detail::compressed_size<Extent> {
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_holder(il.size()) {
: size_holder(il.size()), data_(il.begin()) {
if constexpr (Extent != dynamic_extent) {
assert(il.size() == Extent);
}
Expand All @@ -191,7 +192,7 @@ class span : public detail::compressed_size<Extent> {
int> = 0>
constexpr explicit(Extent != dynamic_extent && OtherExtent == dynamic_extent)
span(const span<OtherElementType, OtherExtent>& s) noexcept
: data_(s.data()), size_holder(s.size()) {
: size_holder(s.size()), data_(s.data()) {
if constexpr (Extent != dynamic_extent) {
assert(s.size() == Extent);
}
Expand Down Expand Up @@ -357,6 +358,33 @@ auto as_writable_bytes(span<ElementType, Extent> s) noexcept
return return_type{reinterpret_cast<std::byte*>(s.data()), s.size_bytes()};
}

// [span.tuple] Tuple interface for fixed-size span (P3786R2).
// Lives in beman::span so ADL picks it up for structured bindings on span<T, N>.
template <std::size_t I, class ElementType, std::size_t Extent>
constexpr ElementType& get(span<ElementType, Extent> s) noexcept {
static_assert(Extent != dynamic_extent, "beman::span::get<I> requires a fixed-extent span");
static_assert(I < Extent, "beman::span::get<I>: index out of range");
return s[I];
}

} // namespace beman::span

// std::tuple_size / std::tuple_element specializations for fixed-size span (P3786R2).
// dynamic_extent is excluded via constrained partial specialization, so
// std::tuple_size<span<T>> falls back to the (undefined) primary template -
// SFINAE-friendly for the tuple-like concept.
namespace std {

template <class ElementType, std::size_t Extent>
requires(Extent != ::beman::span::dynamic_extent)
struct tuple_size<::beman::span::span<ElementType, Extent>> : integral_constant<std::size_t, Extent> {};

template <std::size_t I, class ElementType, std::size_t Extent>
requires(Extent != ::beman::span::dynamic_extent && I < Extent)
struct tuple_element<I, ::beman::span::span<ElementType, Extent>> {
using type = ElementType&;
};

} // namespace std

#endif // BEMAN_SPAN_SPAN_HPP
53 changes: 53 additions & 0 deletions tests/beman/span/span.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <initializer_list>
#include <numeric>
#include <ranges>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
Expand Down Expand Up @@ -657,6 +658,58 @@ TEST(SpanConstexpr, size_and_access) {
EXPECT_EQ(s.size(), 3u);
}

namespace tuple_test_detail {
template <class T, class = void>
struct has_tuple_size : std::false_type {};

template <class T>
struct has_tuple_size<T, std::void_t<decltype(std::tuple_size<T>::value)>> : std::true_type {};
} // namespace tuple_test_detail

TEST(SpanTuple, tuple_size_value) {
static_assert(std::tuple_size_v<bsp::span<int, 3>> == 3);
static_assert(std::tuple_size_v<bsp::span<const double, 5>> == 5);
static_assert(std::tuple_size_v<bsp::span<int, 0>> == 0);
}

TEST(SpanTuple, tuple_size_top_level_const_ignored) { static_assert(std::tuple_size_v<const bsp::span<int, 3>> == 3); }

TEST(SpanTuple, tuple_element_yields_reference) {
static_assert(std::is_same_v<std::tuple_element_t<0, bsp::span<int, 3>>, int&>);
static_assert(std::is_same_v<std::tuple_element_t<2, bsp::span<const double, 5>>, const double&>);
static_assert(std::is_same_v<std::tuple_element_t<0, const bsp::span<int, 3>>, int&>);
}

TEST(SpanTuple, get_returns_reference_to_underlying_element) {
int arr[] = {10, 20, 30};
bsp::span<int, 3> s(arr);

static_assert(std::is_same_v<decltype(get<0>(s)), int&>);
EXPECT_EQ(get<0>(s), 10);
EXPECT_EQ(get<2>(s), 30);

get<1>(s) = 99;
EXPECT_EQ(arr[1], 99);
}

TEST(SpanTuple, structured_binding) {
int arr[] = {1, 2, 3};
bsp::span<int, 3> s(arr);

auto& [a, b, c] = s;
EXPECT_EQ(a, 1);
EXPECT_EQ(b, 2);
EXPECT_EQ(c, 3);

a = 100;
EXPECT_EQ(arr[0], 100);
}

TEST(SpanTuple, dynamic_extent_excluded_from_tuple_protocol) {
static_assert(tuple_test_detail::has_tuple_size<bsp::span<int, 3>>::value);
static_assert(!tuple_test_detail::has_tuple_size<bsp::span<int>>::value);
}

// ---------------------------------------------------------------------------
// Comparison with std::span (when available)
// ---------------------------------------------------------------------------
Expand Down