diff --git a/ports-of-call/span.hpp b/ports-of-call/span.hpp new file mode 100644 index 00000000..7c117e75 --- /dev/null +++ b/ports-of-call/span.hpp @@ -0,0 +1,103 @@ +#ifndef _PORTS_OF_CALL_SPAN_HPP_ +#define _PORTS_OF_CALL_SPAN_HPP_ + +#include "ports-of-call/portability.hpp" + +#include +#include + +namespace PortsOfCall { + +// ================================================================================================ +// Heavily simplified relative to std::span, but provides a similar concept in a way +// that's portable to GPUs. + +template +class span { + private: + T *ptr_{nullptr}; + std::size_t size_{0}; + + public: + // Member types + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = T*; + using reverse_iterator = std::reverse_iterator; + + // Construct an empty span + constexpr span() noexcept = default; + + // Construct a span from a pointer, along with offsets. + // -- ptr : Pointer to the beginning of the array + // -- count : Number of entries in the span + // For example: + // double* my_array = new double[10] + // span full_range(my_array, 10); // elements 0-9 (all 10 elements in the array) + // span sub_range(my_array+2, 6); // elements 2-7 (total of six elements) + // The full_range object provides access to the full range of my_array. The sub_range + // object provides access to indices 2 <= i < 8 of the array my_array, but the indices + // are shifted so that sub_range[i] is my_array[i+2]. + template + PORTABLE_FUNCTION constexpr span(T *ptr, const SizeType count) + : ptr_{ptr}, size_(count) { + assert(count >= 0); + } + + // Query the size of the range + PORTABLE_FUNCTION constexpr auto size() const { return size_; } + + // Iterator (really a pointer) to the beginning of the range, providing mutable access. + PORTABLE_FUNCTION constexpr T *begin() { return ptr_; } + + // Iterator (really a pointer) to the beginning of the range, providing constant access. + PORTABLE_FUNCTION constexpr const T *begin() const { return ptr_; } + + // Iterator (really a pointer) to the beginning of the range, providing constant access. + PORTABLE_FUNCTION constexpr const T *cbegin() const { return ptr_; } + + // Iterator (really a pointer) to the end of the range, providing mutable access. + PORTABLE_FUNCTION constexpr T *end() { return ptr_ + size_; } + + // Iterator (really a pointer) to the beginning of the range, providing constant access. + PORTABLE_FUNCTION constexpr const T *end() const { return ptr_ + size_; } + + // Iterator (really a pointer) to the beginning of the range, providing constant access. + PORTABLE_FUNCTION constexpr const T *cend() const { return ptr_ + size_; } + + // Index operator to obtain mutable access to an element of the range. + template + PORTABLE_FUNCTION constexpr T &operator[](const Index &index) { + assert(index >= 0); + assert(static_cast(index) < size_); + return *(ptr_ + index); + } + + // Index operator to obtain constant access to an element of the range. + template + PORTABLE_FUNCTION constexpr const T &operator[](const Index index) const { + assert(index >= static_cast(0)); + assert(index < static_cast(size_)); + return *(ptr_ + index); + } +}; + +// ================================================================================================ + +template +PORTABLE_FUNCTION constexpr auto make_span(T *const pointer, + const SizeType count) { + return span(pointer, count); +} + +// ================================================================================================ + +} // end namespace PortsOfCall + +#endif // #ifndef _PORTS_OF_CALL_SPAN_HPP_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bd69b4ae..a97407ce 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -64,4 +64,9 @@ target_link_libraries(test_portsofcall include(Catch) catch_discover_tests(test_portsofcall) -target_sources(test_portsofcall PRIVATE test_portability.cpp test_array.cpp) +target_sources(test_portsofcall + PRIVATE + test_portability.cpp + test_array.cpp + test_span.cpp +) diff --git a/test/test_span.cpp b/test/test_span.cpp new file mode 100644 index 00000000..d63530f0 --- /dev/null +++ b/test/test_span.cpp @@ -0,0 +1,103 @@ +#include "ports-of-call/span.hpp" + +#ifndef CATCH_CONFIG_FAST_COMPILE +#define CATCH_CONFIG_FAST_COMPILE +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +namespace span_test { + +template +constexpr static bool really_const = std::is_const>::value; +} + +TEST_CASE("span", "[util][span]") { + using std::begin; + using std::cbegin; + using std::cend; + using std::end; + + using span_test::really_const; + + SECTION("begin/end iteration") { + SECTION("with non-zero size") { + std::vector data0 = {1, 2, 3}; + PortsOfCall::span data(data0.data(), data0.size()); + + auto b = begin(data); + auto e = end(data); + + CHECK(std::distance(b, e) == 3); + CHECK(b + 3 == e); + CHECK(not really_const); + CHECK(not really_const); + + auto cb = cbegin(data); + auto ce = cend(data); + + CHECK(really_const); + CHECK(really_const); + } + } + + SECTION("operator[]") { + SECTION("with non-zero size") { + SECTION("non-const data") { + std::vector data0{1, 2, 3}; + PortsOfCall::span data(data0.data(), data0.size()); + + for (int i = 0; i < 3; ++i) { + CHECK(data[i] == i + 1); + } + CHECK(not really_const); + } + + SECTION("with const data") { + std::vector const data0{1, 2, 3}; + PortsOfCall::span data(data0.data(), data0.size()); + + for (int i = 0; i < 3; ++i) { + CHECK(data[i] == i + 1); + } + CHECK(really_const); + } + } + } + + SECTION("range-based for") { + constexpr int N{10}; + std::vector vec(N); + float *ptr = vec.data(); + PortsOfCall::span span(ptr, N); + float const denom = static_cast(1) / static_cast(N); + int n{0}; + for (auto &x : span) { + x = static_cast(n++) * denom; + } + for (int i{0}; i < N; ++i) { + REQUIRE_THAT(span[i], Catch::Matchers::WithinRel(static_cast(i) * denom)); + } + } + + SECTION("STL algorithms") { + constexpr int N{10}; + std::vector vec(N); + PortsOfCall::span span(vec.data(), N); + std::fill(span.begin(), span.end(), 42); + bool all42 = + std::all_of(span.begin(), span.end(), [](int const x) { return x == 42; }); + CHECK(all42); + std::iota(span.begin(), span.end(), 1); + int sum = std::accumulate(span.begin(), span.end(), 5); + CHECK(sum == 60); + } +}