From 7c0456def7f93545eab43fb85942271052475150 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Tue, 8 Oct 2024 13:55:54 -0600 Subject: [PATCH 01/12] initial commit --- ports-of-call/span.hh | 375 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 ports-of-call/span.hh diff --git a/ports-of-call/span.hh b/ports-of-call/span.hh new file mode 100644 index 0000000..36882c4 --- /dev/null +++ b/ports-of-call/span.hh @@ -0,0 +1,375 @@ +#ifndef PORTS_OF_CALL_SPAN_HH_ +#define PORTS_OF_CALL_SPAN_HH_ + +#include +#include + +#include +#include +#include +#include + +namespace PortsOfCall::span { + +using std::byte; +using std::void_t; + +// set dynamic_extent to maximum integer size +constexpr std::size_t dynamic_extent = SIZE_MAX; + +// forward declare +template +class span; + +namespace detail { + +using std::data; +using std::size; + +// object to handle storage of span +template +struct span_storage { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E *p_ptr, std::size_t /*unused*/) noexcept : ptr(p_ptr) {} + + E *ptr = nullptr; + static constexpr std::size_t size = S; +}; + +// specialization for dynamic_extent +template +struct span_storage { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E *p_ptr, std::size_t p_size) noexcept + : ptr(p_ptr), size(p_size) {} + + E *ptr = nullptr; + std::size_t size = 0; +}; + +// some metaprogramming +template +using uncvref_t = typename std::remove_cv::type>::type; + +template +struct is_span : std::false_type {}; + +template +struct is_span> : std::true_type {}; + +template +struct is_std_array : std::false_type {}; + +template +struct is_std_array> : std::true_type {}; + +template +struct has_size_and_data : std::false_type {}; + +template +struct has_size_and_data())), + decltype(detail::data(std::declval()))>> + : std::true_type {}; + +template > +struct is_container { + static constexpr bool value = !is_span::value && !is_std_array::value && + !std::is_array::value && has_size_and_data::value; +}; + +template +using remove_pointer_t = class std::remove_pointer::type; + +template +struct is_container_element_type_compatible : std::false_type {}; + +// checks to insure that container T and element E are compatible +template +struct is_container_element_type_compatible< + T, E, + typename std::enable_if()))>::type, + void>::value && + std::is_convertible()))> (*)[], + E (*)[]>::value>::type> : std::true_type { +}; + +template +struct is_complete : std::false_type {}; + +template +struct is_complete : std::true_type {}; + +} // namespace detail + +template +class span { + static_assert(std::is_object::value, + "A span's ElementType must be an object type (not a " + "reference type or void)"); + static_assert(detail::is_complete::value, + "A span's ElementType must be a complete type (not a forward " + "declaration)"); + static_assert(!std::is_abstract::value, + "A span's ElementType cannot be an abstract class type"); + + using storage_type = detail::span_storage; + + public: + // constants and types + using element_type = ElementType; + using value_type = typename std::remove_cv::type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = element_type *; + using const_pointer = const element_type *; + using reference = element_type &; + using const_reference = const element_type &; + using iterator = pointer; + using reverse_iterator = std::reverse_iterator; + + static constexpr size_type extent = Extent; + + // [span.cons], span constructors, copy, assignment, and destructor + template ::type = 0> + constexpr span() noexcept {} + + constexpr span(pointer ptr, size_type count) : storage_(ptr, count) {} + + constexpr span(pointer first_elem, pointer last_elem) + : storage_(first_elem, last_elem - first_elem) { + TCB_SPAN_EXPECT(extent == dynamic_extent || + last_elem - first_elem == static_cast(extent)); + } + + template ::value, + int>::type = 0> + constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) {} + + template &, ElementType>::value, + int>::type = 0> + constexpr span(std::array &arr) noexcept : storage_(arr.data(), N) {} + + template &, ElementType>::value, + int>::type = 0> + constexpr span(const std::array &arr) noexcept : storage_(arr.data(), N) {} + + template ::value && + detail::is_container_element_type_compatible< + Container &, ElementType>::value, + int>::type = 0> + constexpr span(Container &cont) : storage_(detail::data(cont), detail::size(cont)) {} + + template ::value && + detail::is_container_element_type_compatible< + const Container &, ElementType>::value, + int>::type = 0> + constexpr span(const Container &cont) + : storage_(detail::data(cont), detail::size(cont)) {} + + constexpr span(const span &other) noexcept = default; + + template ::value, + int>::type = 0> + constexpr span(const span &other) noexcept + : storage_(other.data(), other.size()) {} + + ~span() noexcept = default; + + constexpr span &operator=(const span &other) noexcept = default; + + // [span.sub], span subviews + template + constexpr span first() const { + TCB_SPAN_EXPECT(Count <= size()); + return {data(), Count}; + } + + template + constexpr span last() const { + TCB_SPAN_EXPECT(Count <= size()); + return {data() + (size() - Count), Count}; + } + + template + using subspan_return_t = + span; + + template + constexpr subspan_return_t subspan() const { + TCB_SPAN_EXPECT(Offset <= size() && + (Count == dynamic_extent || Offset + Count <= size())); + return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset}; + } + + constexpr span first(size_type count) const { + TCB_SPAN_EXPECT(count <= size()); + return {data(), count}; + } + + constexpr span last(size_type count) const { + TCB_SPAN_EXPECT(count <= size()); + return {data() + (size() - count), count}; + } + + constexpr span + subspan(size_type offset, size_type count = dynamic_extent) const { + TCB_SPAN_EXPECT(offset <= size() && + (count == dynamic_extent || offset + count <= size())); + return {data() + offset, count == dynamic_extent ? size() - offset : count}; + } + + // [span.obs], span observers + constexpr size_type size() const noexcept { return storage_.size; } + + constexpr size_type size_bytes() const noexcept { + return size() * sizeof(element_type); + } + + [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } + + // [span.elem], span element access + constexpr reference operator[](size_type idx) const { + TCB_SPAN_EXPECT(idx < size()); + return *(data() + idx); + } + + constexpr reference front() const { + TCB_SPAN_EXPECT(!empty()); + return *data(); + } + + constexpr reference back() const { + TCB_SPAN_EXPECT(!empty()); + return *(data() + (size() - 1)); + } + + constexpr pointer data() const noexcept { return storage_.ptr; } + + // [span.iterators], span iterator support + constexpr iterator begin() const noexcept { return data(); } + + 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()); } + + private: + storage_type storage_{}; +}; + +template +span(T (&)[N]) -> span; + +template +span(std::array &) -> span; + +template +span(const std::array &) -> span; + +template +span(Container &) -> span()))>::type>; + +template +span(const Container &) -> span; + +template +constexpr span make_span(span s) noexcept { + return s; +} + +template +constexpr span make_span(T (&arr)[N]) noexcept { + return {arr}; +} + +template +constexpr span make_span(std::array &arr) noexcept { + return {arr}; +} + +template +constexpr span make_span(const std::array &arr) noexcept { + return {arr}; +} + +template +constexpr span()))>::type> +make_span(Container &cont) { + return {cont}; +} + +template +constexpr span make_span(const Container &cont) { + return {cont}; +} + +template +span +as_bytes(span s) noexcept { + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +template ::value, int>::type = 0> +span +as_writable_bytes(span s) noexcept { + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +template +constexpr auto get(span s) -> decltype(s[N]) { + return s[N]; +} + +} // namespace PortsOfCall::span + +namespace std { + +template +class tuple_size> + : public integral_constant {}; + +template +class tuple_size< + PortsOfCall::span::span>; // not + // defined + +template +class tuple_element> { + public: + static_assert(Extent != PortsOfCall::span::dynamic_extent && I < Extent, ""); + using type = ElementType; +}; + +} // end namespace std + +#endif From 47d5c2df04e810510cb9ca8775302845c3e761db Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Tue, 8 Oct 2024 14:57:38 -0600 Subject: [PATCH 02/12] add tests --- ports-of-call/span.hh | 32 +++++-------------- test/CMakeLists.txt | 2 +- test/test_span.cpp | 71 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 test/test_span.cpp diff --git a/ports-of-call/span.hh b/ports-of-call/span.hh index 36882c4..d18f909 100644 --- a/ports-of-call/span.hh +++ b/ports-of-call/span.hh @@ -141,10 +141,7 @@ class span { constexpr span(pointer ptr, size_type count) : storage_(ptr, count) {} constexpr span(pointer first_elem, pointer last_elem) - : storage_(first_elem, last_elem - first_elem) { - TCB_SPAN_EXPECT(extent == dynamic_extent || - last_elem - first_elem == static_cast(extent)); - } + : storage_(first_elem, last_elem - first_elem) {} template constexpr span first() const { - TCB_SPAN_EXPECT(Count <= size()); return {data(), Count}; } template constexpr span last() const { - TCB_SPAN_EXPECT(Count <= size()); return {data() + (size() - Count), Count}; } @@ -221,26 +216,24 @@ class span { template constexpr subspan_return_t subspan() const { - TCB_SPAN_EXPECT(Offset <= size() && (Count == dynamic_extent || Offset + Count <= size())); - return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset}; + return {data() + Offset, + Count != dynamic_extent ? Count : size() - Offset}; } constexpr span first(size_type count) const { - TCB_SPAN_EXPECT(count <= size()); return {data(), count}; } constexpr span last(size_type count) const { - TCB_SPAN_EXPECT(count <= size()); return {data() + (size() - count), count}; } constexpr span subspan(size_type offset, size_type count = dynamic_extent) const { - TCB_SPAN_EXPECT(offset <= size() && (count == dynamic_extent || offset + count <= size())); - return {data() + offset, count == dynamic_extent ? size() - offset : count}; + return {data() + offset, + count == dynamic_extent ? size() - offset : count}; } // [span.obs], span observers @@ -253,20 +246,11 @@ class span { [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } // [span.elem], span element access - constexpr reference operator[](size_type idx) const { - TCB_SPAN_EXPECT(idx < size()); - return *(data() + idx); - } + constexpr reference operator[](size_type idx) const { return *(data() + idx); } - constexpr reference front() const { - TCB_SPAN_EXPECT(!empty()); - return *data(); - } + constexpr reference front() const { return *data(); } - constexpr reference back() const { - TCB_SPAN_EXPECT(!empty()); - return *(data() + (size() - 1)); - } + constexpr reference back() const { return *(data() + (size() - 1)); } constexpr pointer data() const noexcept { return storage_.ptr; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bd69b4a..b0d6ee8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -64,4 +64,4 @@ 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 0000000..789c8a3 --- /dev/null +++ b/test/test_span.cpp @@ -0,0 +1,71 @@ +#include "ports-of-call/span.hh" +#include + +#ifndef CATCH_CONFIG_FAST_COMPILE +#define CATCH_CONFIG_FAST_COMPILE +#include +#endif + +#include + +#define FIXED 10 + +using namespace PortsOfCall::span; + +template +auto span_require(S &&s, I &&i) { + REQUIRE(s.size() == N); + REQUIRE(s.data() == i); + REQUIRE(s.begin() == std::begin(i)); + REQUIRE(s.end() == std::end(i)); +} + +TEST_CASE("default construction", "[PortsOfCall::span]") { + static_assert(std::is_nothrow_default_constructible>::value, ""); + static_assert(std::is_nothrow_constructible>::value, ""); + + static_assert(!std::is_nothrow_constructible>::value, ""); + + SECTION("dynamic size") { + constexpr span s{}; + static_assert(s.size() == 0, "default constructed dynamic size span has size != 0"); + static_assert(s.data() == 0, + "default constructed dynamic size span has data != nullptr"); + static_assert(s.begin() == s.end(), + "default constructed dynamic size span has begin != end"); + } + + SECTION("fixed size") { + constexpr span s{}; + static_assert(s.size() == 0, "default constructed fixed size span has size != 0"); + static_assert(s.data() == 0, + "default constructed fixed size span has data != nullptr"); + static_assert(s.begin() == s.end(), + "default constructed fixed size span has begin != end"); + } +} + +TEST_CASE("iter, extent construction", "[PortsOfCall::span]") { + static_assert(std::is_constructible, int *, int>::value, + "span(int*, int) is not constructible"); + static_assert(std::is_constructible, int *, int>::value, + "span(int*, int) is not constructible"); + static_assert(std::is_constructible, const int *, int>::value, + "span(const int*, int) is not constructible"); + + static_assert(std::is_constructible, int *, int>::value, + "span(int*, int) is not constructible"); + static_assert(std::is_constructible, int *, int>::value, + "span(int*, int) is not constructible"); + static_assert(std::is_constructible, const int *, int>::value, + "span(const int*, int) is not constructible"); + + int arr[] = {0, 1, 2}; + + span s(arr, 3); + + SECTION("dynamic") { + span s{arr, arr + 3}; + span_require(s, arr); + } +} From 4ba1ed33972a5c7eb8803bc3112e328dab1dbbd3 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Tue, 8 Oct 2024 15:25:51 -0600 Subject: [PATCH 03/12] more tests --- ports-of-call/span.hh | 114 ++++++++++++++++++++++-------------------- test/test_span.cpp | 62 ++++++++++++++++++++++- 2 files changed, 120 insertions(+), 56 deletions(-) diff --git a/ports-of-call/span.hh b/ports-of-call/span.hh index d18f909..5a0983a 100644 --- a/ports-of-call/span.hh +++ b/ports-of-call/span.hh @@ -53,49 +53,59 @@ struct span_storage { template using uncvref_t = typename std::remove_cv::type>::type; -template -struct is_span : std::false_type {}; +// detector of span +template +struct is_span_oracle : std::false_type {}; -template -struct is_span> : std::true_type {}; +template +struct is_span_oracle> : std::true_type {}; -template -struct is_std_array : std::false_type {}; +template +struct is_span : is_span_oracle::type> {}; + +template +struct is_std_array_oracle : std::false_type {}; + +template +struct is_std_array_oracle> : std::true_type {}; + +template +struct is_std_array : is_std_array_oracle::type> {}; + +template +struct is_array : std::false_type {}; + +template +struct is_array : std::true_type {}; template -struct is_std_array> : std::true_type {}; +struct is_array : std::true_type {}; -template +template struct has_size_and_data : std::false_type {}; -template -struct has_size_and_data())), - decltype(detail::data(std::declval()))>> +template +struct has_size_and_data())), + decltype(std::data(std::declval()))>> : std::true_type {}; -template > -struct is_container { - static constexpr bool value = !is_span::value && !is_std_array::value && - !std::is_array::value && has_size_and_data::value; -}; +template +struct is_compatible_element : std::false_type {}; -template -using remove_pointer_t = class std::remove_pointer::type; +template +struct is_compatible_element()))>> + : std::is_convertible()))>::type (*)[], + E (*)[]> {}; -template -struct is_container_element_type_compatible : std::false_type {}; - -// checks to insure that container T and element E are compatible -template -struct is_container_element_type_compatible< - T, E, - typename std::enable_if()))>::type, - void>::value && - std::is_convertible()))> (*)[], - E (*)[]>::value>::type> : std::true_type { -}; +template +struct is_container + : std::bool_constant::value && !is_array::value && + !is_std_array::value && has_size_and_data::value> {}; + +template +struct is_compatible_container + : std::bool_constant::value && is_compatible_element::value> {}; template struct is_complete : std::false_type {}; @@ -145,40 +155,40 @@ class span { template ::value, int>::type = 0> constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) {} template &, ElementType>::value, int>::type = 0> constexpr span(std::array &arr) noexcept : storage_(arr.data(), N) {} template &, ElementType>::value, int>::type = 0> constexpr span(const std::array &arr) noexcept : storage_(arr.data(), N) {} template ::value && - detail::is_container_element_type_compatible< - Container &, ElementType>::value, - int>::type = 0> - constexpr span(Container &cont) : storage_(detail::data(cont), detail::size(cont)) {} + typename std::enable_if< + E == dynamic_extent && detail::is_container::value && + detail::is_compatible_container::value, + int>::type = 0> + constexpr span(Container &cont) noexcept + : storage_(detail::data(cont), detail::size(cont)) {} - template ::value && - detail::is_container_element_type_compatible< - const Container &, ElementType>::value, - int>::type = 0> - constexpr span(const Container &cont) + template < + class Container, std::size_t E = Extent, + typename std::enable_if< + E == dynamic_extent && detail::is_container::value && + detail::is_compatible_container::value, + int>::type = 0> + constexpr span(const Container &cont) noexcept : storage_(detail::data(cont), detail::size(cont)) {} constexpr span(const span &other) noexcept = default; @@ -216,9 +226,7 @@ class span { template constexpr subspan_return_t subspan() const { - (Count == dynamic_extent || Offset + Count <= size())); - return {data() + Offset, - Count != dynamic_extent ? Count : size() - Offset}; + return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset}; } constexpr span first(size_type count) const { @@ -231,9 +239,7 @@ class span { constexpr span subspan(size_type offset, size_type count = dynamic_extent) const { - (count == dynamic_extent || offset + count <= size())); - return {data() + offset, - count == dynamic_extent ? size() - offset : count}; + return {data() + offset, count == dynamic_extent ? size() - offset : count}; } // [span.obs], span observers diff --git a/test/test_span.cpp b/test/test_span.cpp index 789c8a3..dd7168d 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -62,10 +62,68 @@ TEST_CASE("iter, extent construction", "[PortsOfCall::span]") { int arr[] = {0, 1, 2}; - span s(arr, 3); - SECTION("dynamic") { span s{arr, arr + 3}; span_require(s, arr); } + + SECTION("fixed") { + span s(arr, 3); + span_require(s, arr); + } +} + +TEST_CASE("Carr construction", "[PortsOfCall::span]") { + using int_array_t = int[3]; + using real_array_t = double[3]; + + static_assert(std::is_nothrow_constructible, int_array_t &>::value, ""); + static_assert(!std::is_constructible, int_array_t const &>::value, ""); + static_assert(!std::is_constructible, real_array_t>::value, ""); + + static_assert(std::is_nothrow_constructible, int_array_t &>::value, ""); + static_assert( + std::is_nothrow_constructible, int_array_t const &>::value, ""); + static_assert(!std::is_constructible, real_array_t>::value, ""); + + static_assert(std::is_nothrow_constructible, int_array_t &>::value, ""); + static_assert(!std::is_constructible, int_array_t const &>::value, ""); + static_assert(!std::is_constructible, real_array_t &>::value, ""); + + static_assert(std::is_nothrow_constructible, int_array_t &>::value, + ""); + static_assert( + std::is_nothrow_constructible, int_array_t const &>::value, ""); + static_assert(!std::is_constructible, real_array_t>::value, ""); + + static_assert(!std::is_constructible, int_array_t &>::value, ""); + static_assert(!std::is_constructible, int_array_t const &>::value, ""); + static_assert(!std::is_constructible, real_array_t &>::value, ""); + + static_assert(!std::is_constructible, int_array_t &>::value, ""); + static_assert(!std::is_constructible, int_array_t const &>::value, + ""); + static_assert(!std::is_constructible, real_array_t &>::value, ""); + + int arr[] = {0, 1, 2}; + + SECTION("dynamic") { + span s{arr}; + span_require(s, arr); + } + + SECTION("dynamic const") { + span s{arr}; + span_require(s, arr); + } + + SECTION("fixed") { + span s{arr}; + span_require(s, arr); + } + + SECTION("fixed const") { + span s{arr}; + span_require(s, arr); + } } From 4800fa20fcc4df4e6f7df8ee2cbcbeca1e0d0172 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Wed, 9 Oct 2024 11:54:06 -0600 Subject: [PATCH 04/12] updates to tests and more doc --- ports-of-call/span.hh | 68 ++++++-- test/test_span.cpp | 386 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 409 insertions(+), 45 deletions(-) diff --git a/ports-of-call/span.hh b/ports-of-call/span.hh index 5a0983a..95b98cc 100644 --- a/ports-of-call/span.hh +++ b/ports-of-call/span.hh @@ -144,15 +144,33 @@ class span { static constexpr size_type extent = Extent; // [span.cons], span constructors, copy, assignment, and destructor + // + + // constructs an empty span + // - data() == nullptr + // - size() == 0 template ::type = 0> constexpr span() noexcept {} + // constructs a span that is a view over the range [first, first+count) + // - data() == std::to_address(first) + // - size() == count + // explicit: extent != dynamic_extent + // NB: iterator concepts to further restrict overload resolution constexpr span(pointer ptr, size_type count) : storage_(ptr, count) {} + // constructs a span that is a view over the range [first, last) + // - data() == std::to_address(first) + // - size() == last - first + // explicit: extent != dynamic_extent + // NB: iterator concepts to further restrict overload resolution constexpr span(pointer first_elem, pointer last_elem) : storage_(first_elem, last_elem - first_elem) {} + // constructs a span that is a view over arr + // - data() == std::to_data(arr) + // - size() == N template ::type = 0> constexpr span(const std::array &arr) noexcept : storage_(arr.data(), N) {} + // constructs a span that is a view of cont + // - data() == std::data(cont) + // - size() == std::size(cont) + // NB: C++20 replaces Container concept with Range concept. + // NB: CompatibleContainer(or future Range) concept is exclusive with C-array, + // std::array, and span template ::value && @@ -206,17 +230,31 @@ class span { constexpr span &operator=(const span &other) noexcept = default; - // [span.sub], span subviews + // [span.subviews]-- + // obtains a span that is a view over the first Count elements of this span + // https://en.cppreference.com/w/cpp/container/span/first template constexpr span first() const { return {data(), Count}; } + constexpr span first(size_type count) const { + return {data(), count}; + } + + // obtains a span that is a view over the last Count elements of this span + // https://en.cppreference.com/w/cpp/container/span/last template constexpr span last() const { return {data() + (size() - Count), Count}; } + constexpr span last(size_type count) const { + return {data() + (size() - count), count}; + } + + // obtains a span that is a view over Count elements of this span starting at Offset + // https://en.cppreference.com/w/cpp/container/span/subspan template using subspan_return_t = span first(size_type count) const { - return {data(), count}; - } - - constexpr span last(size_type count) const { - return {data() + (size() - count), count}; - } - constexpr span subspan(size_type offset, size_type count = dynamic_extent) const { return {data() + offset, count == dynamic_extent ? size() - offset : count}; } + // --[span.subviews] - // [span.obs], span observers + // [span.observers]-- + + // returns the number of elements in the span constexpr size_type size() const noexcept { return storage_.size; } + // returns the size of sequence in bytes constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); } + // checks if sequence is empty() [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } - // [span.elem], span element access + // --[span.observers] + + // [span.element_access]-- constexpr reference operator[](size_type idx) const { return *(data() + idx); } constexpr reference front() const { return *data(); } @@ -259,8 +296,9 @@ class span { constexpr reference back() const { return *(data() + (size() - 1)); } constexpr pointer data() const noexcept { return storage_.ptr; } + // --[span.element_access] - // [span.iterators], span iterator support + // [span.iterators]-- constexpr iterator begin() const noexcept { return data(); } constexpr iterator end() const noexcept { return data() + size(); } @@ -268,11 +306,14 @@ class span { constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } constexpr reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } + // --[span.iterators] private: storage_type storage_{}; }; +// deduction guides +// https://en.cppreference.com/w/cpp/container/span/deduction_guides template span(T (&)[N]) -> span; @@ -321,6 +362,9 @@ constexpr span make_span(const Container & return {cont}; } +// as_bytes, as_writable_bytes +// obtains a view to the elements of span s +// https://en.cppreference.com/w/cpp/container/span/as_bytes template span diff --git a/test/test_span.cpp b/test/test_span.cpp index dd7168d..5ac5cfe 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -10,21 +10,84 @@ #define FIXED 10 +namespace test { + using namespace PortsOfCall::span; +namespace detail { + +// equal, search are the std::equal, std::search but made constexpr +// this is the C++20 implementation +template +constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2) { + for (; first1 != last1; ++first1, ++first2) + if (!(*first1 == *first2)) return false; + + return true; +} + +template +constexpr ForwardIt1 search(ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first, + ForwardIt2 s_last) { + while (true) { + ForwardIt1 it = first; + for (ForwardIt2 s_it = s_first;; ++it, ++s_it) { + if (s_it == s_last) return first; + if (it == last) return last; + if (!(*it == *s_it)) break; + } + ++first; + } +} + +// same as prior, moved to constexpr +template +constexpr void generate(ForwardIt first, ForwardIt last, Generator g) { + for (; first != last; ++first) + *first = g(); +} + +template +[[nodiscard]] +constexpr auto slide(span s, std::size_t offset, std::size_t width) { + return s.subspan(offset, offset + width <= s.size() ? width : 0U); +} + +template +constexpr bool starts_with(span data, span prefix) { + return data.size() >= prefix.size() && + equal(prefix.begin(), prefix.end(), data.begin()); +} + +template +constexpr bool ends_with(span data, span suffix) { + return data.size() >= suffix.size() && + equal(data.end() - suffix.size(), data.end(), suffix.end() - suffix.size()); +} + +template +constexpr bool contains(span s, span sub) { + return search(s.begin(), s.end(), sub.begin(), sub.end()) != s.end(); +} + +} // namespace detail + template auto span_require(S &&s, I &&i) { REQUIRE(s.size() == N); REQUIRE(s.data() == i); - REQUIRE(s.begin() == std::begin(i)); - REQUIRE(s.end() == std::end(i)); + REQUIRE(s.begin() == i); + REQUIRE(s.end() == i + N); } -TEST_CASE("default construction", "[PortsOfCall::span]") { - static_assert(std::is_nothrow_default_constructible>::value, ""); - static_assert(std::is_nothrow_constructible>::value, ""); +TEST_CASE("span default construction", "[PortsOfCall::span]") { + static_assert(std::is_nothrow_default_constructible>::value, + "span is not nothrow default constructible"); + static_assert(std::is_nothrow_constructible>::value, + "span is not nothrow constructible"); - static_assert(!std::is_nothrow_constructible>::value, ""); + static_assert(!std::is_nothrow_constructible>::value, + "span is nothrow construcible"); SECTION("dynamic size") { constexpr span s{}; @@ -45,7 +108,7 @@ TEST_CASE("default construction", "[PortsOfCall::span]") { } } -TEST_CASE("iter, extent construction", "[PortsOfCall::span]") { +TEST_CASE("span iter, extent construction", "[PortsOfCall::span]") { static_assert(std::is_constructible, int *, int>::value, "span(int*, int) is not constructible"); static_assert(std::is_constructible, int *, int>::value, @@ -73,39 +136,39 @@ TEST_CASE("iter, extent construction", "[PortsOfCall::span]") { } } -TEST_CASE("Carr construction", "[PortsOfCall::span]") { - using int_array_t = int[3]; - using real_array_t = double[3]; +template +constexpr auto array_construcible_correct() { + static_assert(std::is_nothrow_constructible, C &>::value, ""); + static_assert(!std::is_constructible, const C &>::value, ""); + static_assert(std::is_nothrow_constructible, C &>::value, ""); + static_assert(std::is_nothrow_constructible, const C &>::value, ""); - static_assert(std::is_nothrow_constructible, int_array_t &>::value, ""); - static_assert(!std::is_constructible, int_array_t const &>::value, ""); - static_assert(!std::is_constructible, real_array_t>::value, ""); + static_assert(std::is_nothrow_constructible, C &>::value, ""); + static_assert(!std::is_constructible, const C &>::value, ""); + static_assert(std::is_nothrow_constructible, C &>::value, ""); + static_assert(std::is_nothrow_constructible, const C &>::value, ""); +} - static_assert(std::is_nothrow_constructible, int_array_t &>::value, ""); - static_assert( - std::is_nothrow_constructible, int_array_t const &>::value, ""); - static_assert(!std::is_constructible, real_array_t>::value, ""); +template +constexpr auto array_construcible_correct_oversized() { + constexpr auto M = N + 1; + static_assert(!std::is_constructible, C &>::value, ""); + static_assert(!std::is_constructible, const C &>::value, ""); - static_assert(std::is_nothrow_constructible, int_array_t &>::value, ""); - static_assert(!std::is_constructible, int_array_t const &>::value, ""); - static_assert(!std::is_constructible, real_array_t &>::value, ""); + static_assert(!std::is_constructible, C &>::value, ""); + static_assert(!std::is_constructible, const C &>::value, ""); +} - static_assert(std::is_nothrow_constructible, int_array_t &>::value, - ""); - static_assert( - std::is_nothrow_constructible, int_array_t const &>::value, ""); - static_assert(!std::is_constructible, real_array_t>::value, ""); +TEST_CASE("span Carr construction", "[PortsOfCall::span]") { + using int_array_t = int[3]; + using real_array_t = double[3]; - static_assert(!std::is_constructible, int_array_t &>::value, ""); - static_assert(!std::is_constructible, int_array_t const &>::value, ""); - static_assert(!std::is_constructible, real_array_t &>::value, ""); + array_construcible_correct(); + array_construcible_correct(); - static_assert(!std::is_constructible, int_array_t &>::value, ""); - static_assert(!std::is_constructible, int_array_t const &>::value, - ""); - static_assert(!std::is_constructible, real_array_t &>::value, ""); + array_construcible_correct_oversized(); - int arr[] = {0, 1, 2}; + int_array_t arr = {0, 1, 2}; SECTION("dynamic") { span s{arr}; @@ -127,3 +190,260 @@ TEST_CASE("Carr construction", "[PortsOfCall::span]") { span_require(s, arr); } } + +TEST_CASE("span std::array construction", "[PortsOfCall::span]") { + using int_array_t = std::array; + using real_array_t = std::array; + using zero_array_t = std::array; + + array_construcible_correct(); + array_construcible_correct(); + array_construcible_correct(); + + array_construcible_correct_oversized(); + array_construcible_correct_oversized(); + + int_array_t arr = {0, 1, 2}; + + SECTION("dynamic") { + span s{arr}; + span_require(s, arr.data()); + } + + SECTION("dynamic const") { + span s{arr}; + span_require(s, arr.data()); + } + + SECTION("fixed") { + span s{arr}; + span_require(s, arr.data()); + } + + SECTION("fixed const") { + span s{arr}; + span_require(s, arr.data()); + } +} + +template +constexpr auto container_construcible_correct() { + using E = typename C::value_type; + static_assert(std::is_nothrow_constructible, C &>::value, ""); + static_assert(!std::is_constructible, const C &>::value, ""); + static_assert(std::is_nothrow_constructible, C &>::value, ""); + static_assert(std::is_nothrow_constructible, const C &>::value, ""); + + static_assert(!std::is_constructible, C &>::value, ""); + static_assert(!std::is_constructible, const C &>::value, ""); + static_assert(!std::is_constructible, C &>::value, ""); + static_assert(!std::is_constructible, const C &>::value, ""); +} + +TEST_CASE("span container construction", "[PortsOfCall::span]") { + using vec_t = std::vector; + + container_construcible_correct(); + vec_t varr = {1, 2, 3}; + + SECTION("dynamic") { + span s{varr}; + span_require(s, varr.data()); + } + + SECTION("dynamic const") { + span s{varr}; + span_require(s, varr.data()); + } + // NB: fixed/fixed const is equivilant to std::array construction +} + +TEST_CASE("span different size span construction", "PortsOfCall::span") { + using zero_span = span; + using zero_const_span = span; + + using big_span = span; + using big_const_span = span; + + using dynamic_span = span; + using dynamic_const_span = span; + + // + static_assert(std::is_trivially_copyable::value, ""); + static_assert(std::is_trivially_move_constructible::value, ""); + + static_assert(!std::is_constructible::value, ""); + + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + + static_assert(std::is_nothrow_constructible::value, ""); + // + static_assert(std::is_trivially_copyable::value, ""); + static_assert(std::is_trivially_move_constructible::value, ""); + + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + + static_assert(std::is_nothrow_constructible::value, ""); + // + // + static_assert(std::is_trivially_copyable::value, ""); + static_assert(std::is_trivially_move_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, ""); + + static_assert(std::is_nothrow_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, + ""); + // + static_assert(std::is_trivially_copyable::value, ""); + static_assert(std::is_trivially_move_constructible::value, ""); + + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, + ""); + // + // + static_assert(std::is_trivially_copyable::value, ""); + static_assert(std::is_trivially_move_constructible::value, ""); + + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + + static_assert(std::is_nothrow_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, ""); + + // + static_assert(std::is_trivially_copyable::value, ""); + static_assert(std::is_trivially_move_constructible::value, ""); + + static_assert(std::is_nothrow_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, + ""); + static_assert(std::is_nothrow_constructible::value, ""); + static_assert(std::is_nothrow_constructible::value, + ""); + static_assert(std::is_nothrow_constructible::value, + ""); + constexpr zero_const_span s0{}; + constexpr dynamic_const_span d{s0}; + + static_assert(d.size() == 0, ""); + static_assert(d.data() == nullptr, ""); + static_assert(d.begin() == d.end(), ""); +} + +TEST_CASE("span member subview operations", "[PortsOfCall::span]") { + + int arr[] = {1, 2, 3, 4, 5}; + span s{arr}; + + SECTION("first") { + auto f = s.first<3>(); + + static_assert(std::is_same>::value, ""); + span_require(f, arr); + } + + SECTION("last") { + auto l = s.last<3>(); + + static_assert(std::is_same>::value, ""); + REQUIRE(l.size() == 3); + REQUIRE(l.data() == arr + 2); + REQUIRE(l.begin() == arr + 2); + REQUIRE(l.end() == std::end(arr)); + } + + SECTION("subspan") { + auto ss = s.subspan<1, 2>(); + + static_assert(std::is_same>::value, ""); + REQUIRE(ss.size() == 2); + REQUIRE(ss.data() == arr + 1); + REQUIRE(ss.begin() == arr + 1); + REQUIRE(ss.end() == arr + 3); + } + + SECTION("first(n)") { + auto f = s.first(3); + + static_assert(std::is_same>::value, ""); + span_require(f, arr); + } + + SECTION("last(n)") { + auto l = s.last(3); + + static_assert(std::is_same>::value, ""); + REQUIRE(l.size() == 3); + REQUIRE(l.data() == arr + 2); + REQUIRE(l.begin() == arr + 2); + REQUIRE(l.end() == std::end(arr)); + } + + SECTION("subspan(n)") { + auto ss = s.subspan(1, 2); + + static_assert(std::is_same>::value, ""); + REQUIRE(ss.size() == 2); + REQUIRE(ss.data() == arr + 1); + REQUIRE(ss.begin() == arr + 1); + REQUIRE(ss.end() == arr + 3); + } +} + +TEST_CASE("span observers", "[PortsOfCall::span]") { + constexpr span empty{}; + static_assert(empty.size() == 0, ""); + static_assert(empty.empty(), ""); + + constexpr int arr[] = {1, 2, 3}; + static_assert(span{arr}.size() == 3, ""); + static_assert(!span{arr}.empty(), ""); +} + +TEST_CASE("span element access", "[PortsOfCall::span]") { + constexpr int arr[] = {1, 2, 3}; + span s{arr}; + + REQUIRE(s[0] == arr[0]); + REQUIRE(s[1] == arr[1]); + REQUIRE(s[2] == arr[2]); +} + +// adopted from https://en.cppreference.com/w/cpp/container/span +TEST_CASE("span typical", "[PortsOfCall::span]") { + constexpr int a[]{0, 1, 2, 3, 4, 5, 6, 7, 8}; + constexpr int b[]{8, 7, 6}; + constexpr static std::size_t width{6}; + + constexpr int res[][width] = { + {0, 1, 2, 3, 4, 5}, {1, 2, 3, 4, 5, 6}, {2, 3, 4, 5, 6, 7}, {3, 4, 5, 6, 7, 8}}; + + for (std::size_t offset{};; ++offset) + if (auto s = detail::slide(span{a}, offset, width); !s.empty()) + REQUIRE(detail::equal(s.begin(), s.end(), std::begin(res[offset]))); + else + break; + + static_assert("" && detail::starts_with(span{a}, span{a, 4}) && + detail::starts_with(span{a + 1, 4}, span{a + 1, 3}) && + !detail::starts_with(span{a}, span{b}) && + !detail::starts_with(span{a, 8}, span{a + 1, 3}) && + detail::ends_with(span{a}, span{a + 6, 3}) && + !detail::ends_with(span{a}, span{a + 6, 2}) && + detail::contains(span{a}, span{a + 1, 4}) && + !detail::contains(span{a, 8}, span{a, 9})); +} + +} // namespace test From ee8c5cc45cd87ed1364dbe91dbde50fc3a254762 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Wed, 9 Oct 2024 13:37:13 -0600 Subject: [PATCH 05/12] more test changes --- test/test_span.cpp | 311 +++++++++++++++++++++++++++------------------ 1 file changed, 190 insertions(+), 121 deletions(-) diff --git a/test/test_span.cpp b/test/test_span.cpp index 5ac5cfe..c6e4256 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -8,8 +8,6 @@ #include -#define FIXED 10 - namespace test { using namespace PortsOfCall::span; @@ -72,29 +70,22 @@ constexpr bool contains(span s, span sub) { } // namespace detail -template -auto span_require(S &&s, I &&i) { - REQUIRE(s.size() == N); - REQUIRE(s.data() == i); - REQUIRE(s.begin() == i); - REQUIRE(s.end() == i + N); -} - TEST_CASE("span default construction", "[PortsOfCall::span]") { + static_assert(std::is_nothrow_default_constructible>::value, "span is not nothrow default constructible"); static_assert(std::is_nothrow_constructible>::value, "span is not nothrow constructible"); - static_assert(!std::is_nothrow_constructible>::value, - "span is nothrow construcible"); + static_assert(!std::is_nothrow_constructible>::value, + "span is nothrow construcible"); SECTION("dynamic size") { - constexpr span s{}; - static_assert(s.size() == 0, "default constructed dynamic size span has size != 0"); - static_assert(s.data() == 0, - "default constructed dynamic size span has data != nullptr"); - static_assert(s.begin() == s.end(), + constexpr span static_span; + static_assert(nullptr == static_span.data(), ""); + static_assert(0u == static_span.size(), ""); + + static_assert(static_span.begin() == static_span.end(), "default constructed dynamic size span has begin != end"); } @@ -116,144 +107,216 @@ TEST_CASE("span iter, extent construction", "[PortsOfCall::span]") { static_assert(std::is_constructible, const int *, int>::value, "span(const int*, int) is not constructible"); - static_assert(std::is_constructible, int *, int>::value, - "span(int*, int) is not constructible"); - static_assert(std::is_constructible, int *, int>::value, - "span(int*, int) is not constructible"); - static_assert(std::is_constructible, const int *, int>::value, - "span(const int*, int) is not constructible"); + static_assert(std::is_constructible, int *, int>::value, + "span(int*, int) is not constructible"); + static_assert(std::is_constructible, int *, int>::value, + "span(int*, int) is not constructible"); + static_assert(std::is_constructible, const int *, int>::value, + "span(const int*, int) is not constructible"); int arr[] = {0, 1, 2}; SECTION("dynamic") { span s{arr, arr + 3}; - span_require(s, arr); + REQUIRE(s.size() == 3); + REQUIRE(s.data() == arr); + REQUIRE(s.begin() == arr); + REQUIRE(s.end() == arr + 3); } SECTION("fixed") { span s(arr, 3); - span_require(s, arr); + REQUIRE(s.size() == 3); + REQUIRE(s.data() == arr); + REQUIRE(s.begin() == arr); + REQUIRE(s.end() == arr + 3); } } -template -constexpr auto array_construcible_correct() { - static_assert(std::is_nothrow_constructible, C &>::value, ""); - static_assert(!std::is_constructible, const C &>::value, ""); - static_assert(std::is_nothrow_constructible, C &>::value, ""); - static_assert(std::is_nothrow_constructible, const C &>::value, ""); - - static_assert(std::is_nothrow_constructible, C &>::value, ""); - static_assert(!std::is_constructible, const C &>::value, ""); - static_assert(std::is_nothrow_constructible, C &>::value, ""); - static_assert(std::is_nothrow_constructible, const C &>::value, ""); -} - -template -constexpr auto array_construcible_correct_oversized() { - constexpr auto M = N + 1; - static_assert(!std::is_constructible, C &>::value, ""); - static_assert(!std::is_constructible, const C &>::value, ""); - - static_assert(!std::is_constructible, C &>::value, ""); - static_assert(!std::is_constructible, const C &>::value, ""); -} - TEST_CASE("span Carr construction", "[PortsOfCall::span]") { - using int_array_t = int[3]; - using real_array_t = double[3]; - - array_construcible_correct(); - array_construcible_correct(); + using int_carr_t = int[3]; + static_assert(std::is_nothrow_constructible, int_carr_t &>::value, ""); + static_assert(!std::is_constructible, const int_carr_t &>::value, ""); + static_assert(std::is_nothrow_constructible, int_carr_t &>::value, ""); + static_assert(std::is_nothrow_constructible, const int_carr_t &>::value, + ""); - array_construcible_correct_oversized(); + static_assert(std::is_nothrow_constructible, int_carr_t &>::value, ""); + static_assert(!std::is_constructible, const int_carr_t &>::value, ""); + static_assert(std::is_nothrow_constructible, int_carr_t &>::value, + ""); + static_assert( + std::is_nothrow_constructible, const int_carr_t &>::value, ""); - int_array_t arr = {0, 1, 2}; + static_assert(!std::is_constructible, int_carr_t &>::value, ""); + static_assert(!std::is_constructible, const int_carr_t &>::value, ""); - SECTION("dynamic") { - span s{arr}; - span_require(s, arr); - } - - SECTION("dynamic const") { - span s{arr}; - span_require(s, arr); - } + static_assert(!std::is_constructible, int_carr_t &>::value, ""); + static_assert(!std::is_constructible, const int_carr_t &>::value, + ""); - SECTION("fixed") { - span s{arr}; - span_require(s, arr); + SECTION("regular c-array") { + int arr[] = {0, 1, 2}; + span const_span(arr); + REQUIRE(arr == const_span.data()); + REQUIRE(std::size(arr) == const_span.size()); + for (size_t i = 0; i < const_span.size(); ++i) + REQUIRE(arr[i] == const_span[i]); + span dynamic_span(arr); + REQUIRE(arr == dynamic_span.data()); + REQUIRE(std::size(arr) == dynamic_span.size()); + for (size_t i = 0; i < dynamic_span.size(); ++i) + REQUIRE(arr[i] == dynamic_span[i]); + span static_span(arr); + REQUIRE(arr == static_span.data()); + REQUIRE(std::size(arr) == static_span.size()); + for (size_t i = 0; i < static_span.size(); ++i) + REQUIRE(arr[i] == static_span[i]); } - SECTION("fixed const") { - span s{arr}; - span_require(s, arr); + SECTION("constexpr c-array") { + static constexpr int kArray[] = {5, 4, 3, 2, 1}; + constexpr span dynamic_span(kArray); + static_assert(kArray == dynamic_span.data(), ""); + static_assert(std::size(kArray) == dynamic_span.size(), ""); + static_assert(kArray[0] == dynamic_span[0], ""); + static_assert(kArray[1] == dynamic_span[1], ""); + static_assert(kArray[2] == dynamic_span[2], ""); + static_assert(kArray[3] == dynamic_span[3], ""); + static_assert(kArray[4] == dynamic_span[4], ""); + constexpr span static_span(kArray); + static_assert(kArray == static_span.data(), ""); + static_assert(std::size(kArray) == static_span.size(), ""); + static_assert(kArray[0] == static_span[0], ""); + static_assert(kArray[1] == static_span[1], ""); + static_assert(kArray[2] == static_span[2], ""); + static_assert(kArray[3] == static_span[3], ""); + static_assert(kArray[4] == static_span[4], ""); } } TEST_CASE("span std::array construction", "[PortsOfCall::span]") { - using int_array_t = std::array; - using real_array_t = std::array; - using zero_array_t = std::array; - - array_construcible_correct(); - array_construcible_correct(); - array_construcible_correct(); - - array_construcible_correct_oversized(); - array_construcible_correct_oversized(); - - int_array_t arr = {0, 1, 2}; - - SECTION("dynamic") { - span s{arr}; - span_require(s, arr.data()); - } - - SECTION("dynamic const") { - span s{arr}; - span_require(s, arr.data()); - } - - SECTION("fixed") { - span s{arr}; - span_require(s, arr.data()); - } - - SECTION("fixed const") { - span s{arr}; - span_require(s, arr.data()); - } -} - -template -constexpr auto container_construcible_correct() { - using E = typename C::value_type; - static_assert(std::is_nothrow_constructible, C &>::value, ""); - static_assert(!std::is_constructible, const C &>::value, ""); - static_assert(std::is_nothrow_constructible, C &>::value, ""); - static_assert(std::is_nothrow_constructible, const C &>::value, ""); - - static_assert(!std::is_constructible, C &>::value, ""); - static_assert(!std::is_constructible, const C &>::value, ""); - static_assert(!std::is_constructible, C &>::value, ""); - static_assert(!std::is_constructible, const C &>::value, ""); + // In the following assertions we use std::is_convertible_v, which + // for non-void types is equivalent to checking whether the following + // expression is well-formed: + // + // T obj = std::declval(); + // + // In particular we are checking whether From is implicitly convertible to To, + // which also implies that To is explicitly constructible from From. + + static_assert(std::is_convertible &, span>::value, + "Error: l-value reference to std::array should be convertible to " + "span with dynamic extent."); + static_assert(std::is_convertible &, span>::value, + "Error: l-value reference to std::array should be convertible to " + "span with the same static extent."); + static_assert(std::is_convertible &, span>::value, + "Error: l-value reference to std::array should be convertible to " + "span with dynamic extent."); + static_assert(std::is_convertible &, span>::value, + "Error: l-value reference to std::array should be convertible to " + "span with the same static extent."); + static_assert(std::is_convertible &, span>::value, + "Error: const l-value reference to std::array should be " + "convertible to span with dynamic extent."); + static_assert( + std::is_convertible &, span>::value, + "Error: const l-value reference to std::array should be convertible " + "to span with the same static extent."); + static_assert(std::is_convertible &, span>::value, + "Error: l-value reference to std::array should be " + "convertible to span with dynamic extent."); + static_assert( + std::is_convertible &, span>::value, + "Error: l-value reference to std::array should be convertible " + "to span with the same static extent."); + static_assert( + std::is_convertible &, span>::value, + "Error: const l-value reference to std::array should be " + "convertible to span with dynamic extent."); + static_assert( + std::is_convertible &, span>::value, + "Error: const l-value reference to std::array should be " + "convertible to span with the same static extent."); + + // In the following assertions we use !std::is_constructible_v, which + // is equivalent to checking whether the following expression is malformed: + // + // T obj(std::declval()...); + // + // In particular we are checking that T is not explicitly constructible from + // Args, which also implies that T is not implicitly constructible from Args + // as well. + static_assert(!std::is_constructible, const std::array &>::value, + "Error: span with dynamic extent should not be " + "constructible from const l-value reference to std::array"); + static_assert(!std::is_constructible, std::array &>::value, + "Error: span with dynamic extent should not be constructible " + "from l-value reference to std::array"); + static_assert( + !std::is_constructible, const std::array &>::value, + "Error: span with dynamic extent should not be constructible " + "const from l-value reference to std::array"); + static_assert(!std::is_constructible, std::array &>::value, + "Error: span with static extent should not be constructible " + "from l-value reference to std::array with different extent"); + static_assert(!std::is_constructible, std::array &>::value, + "Error: span with dynamic extent should not be constructible " + "from l-value reference to std::array with different extent"); + static_assert(!std::is_constructible, std::array &>::value, + "Error: span with dynamic extent should not be constructible " + "from l-value reference to std::array"); + + // Note: Constructing a constexpr span from a constexpr std::array does not + // work prior to C++17 due to non-constexpr std::array::data. + std::array array = {{5, 4, 3, 2, 1}}; + span const_span(array); + REQUIRE(array.data() == const_span.data()); + REQUIRE(array.size() == const_span.size()); + for (size_t i = 0; i < const_span.size(); ++i) + REQUIRE(array[i] == const_span[i]); + span dynamic_span(array); + REQUIRE(array.data() == dynamic_span.data()); + REQUIRE(array.size() == dynamic_span.size()); + for (size_t i = 0; i < dynamic_span.size(); ++i) + REQUIRE(array[i] == dynamic_span[i]); + span static_span(array); + REQUIRE(array.data() == static_span.data()); + REQUIRE(array.size() == static_span.size()); + for (size_t i = 0; i < static_span.size(); ++i) + REQUIRE(array[i] == static_span[i]); } TEST_CASE("span container construction", "[PortsOfCall::span]") { using vec_t = std::vector; - container_construcible_correct(); + static_assert(std::is_nothrow_constructible, vec_t &>::value, ""); + static_assert(!std::is_constructible, const vec_t &>::value, ""); + static_assert(std::is_nothrow_constructible, vec_t &>::value, ""); + static_assert(std::is_nothrow_constructible, const vec_t &>::value, ""); + + static_assert(!std::is_constructible, vec_t &>::value, ""); + static_assert(!std::is_constructible, const vec_t &>::value, ""); + static_assert(!std::is_constructible, vec_t &>::value, ""); + static_assert(!std::is_constructible, const vec_t &>::value, ""); + vec_t varr = {1, 2, 3}; SECTION("dynamic") { span s{varr}; - span_require(s, varr.data()); + REQUIRE(s.size() == 3); + REQUIRE(s.data() == varr.data()); + REQUIRE(s.begin() == varr.data()); + REQUIRE(s.end() == varr.data() + 3); } SECTION("dynamic const") { span s{varr}; - span_require(s, varr.data()); + REQUIRE(s.size() == 3); + REQUIRE(s.data() == varr.data()); + REQUIRE(s.begin() == varr.data()); + REQUIRE(s.end() == varr.data() + 3); } // NB: fixed/fixed const is equivilant to std::array construction } @@ -351,7 +414,10 @@ TEST_CASE("span member subview operations", "[PortsOfCall::span]") { auto f = s.first<3>(); static_assert(std::is_same>::value, ""); - span_require(f, arr); + REQUIRE(f.size() == 3); + REQUIRE(f.data() == arr); + REQUIRE(f.begin() == arr); + REQUIRE(f.end() == arr + 3); } SECTION("last") { @@ -378,7 +444,10 @@ TEST_CASE("span member subview operations", "[PortsOfCall::span]") { auto f = s.first(3); static_assert(std::is_same>::value, ""); - span_require(f, arr); + REQUIRE(f.size() == 3); + REQUIRE(f.data() == arr); + REQUIRE(f.begin() == arr); + REQUIRE(f.end() == arr + 3); } SECTION("last(n)") { From 9f85964adecfda37ad39701338412edaff1a0819 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Wed, 9 Oct 2024 15:33:35 -0600 Subject: [PATCH 06/12] add asserts (should move to throws) --- ports-of-call/{span.hh => span.hpp} | 75 ++++++++--- test/test_span.cpp | 187 ++++++++++++++++------------ 2 files changed, 168 insertions(+), 94 deletions(-) rename ports-of-call/{span.hh => span.hpp} (87%) diff --git a/ports-of-call/span.hh b/ports-of-call/span.hpp similarity index 87% rename from ports-of-call/span.hh rename to ports-of-call/span.hpp index 95b98cc..32f6da3 100644 --- a/ports-of-call/span.hh +++ b/ports-of-call/span.hpp @@ -1,14 +1,27 @@ #ifndef PORTS_OF_CALL_SPAN_HH_ #define PORTS_OF_CALL_SPAN_HH_ -#include -#include - #include +#include #include #include +#include +#include #include +#if defined(__cpp_lib_span) // do we already have std::span? + +#include +namespace PortsOfCall::span { + +using std::span; + +} // namespace PortsOfCall::span + +#else + +#define span_EXPECTS(...) assert((__VA_ARGS__)) + namespace PortsOfCall::span { using std::byte; @@ -18,7 +31,7 @@ using std::void_t; constexpr std::size_t dynamic_extent = SIZE_MAX; // forward declare -template +template class span; namespace detail { @@ -27,7 +40,7 @@ using std::data; using std::size; // object to handle storage of span -template +template struct span_storage { constexpr span_storage() noexcept = default; @@ -38,7 +51,7 @@ struct span_storage { }; // specialization for dynamic_extent -template +template struct span_storage { constexpr span_storage() noexcept = default; @@ -81,6 +94,7 @@ struct is_array : std::true_type {}; template struct is_array : std::true_type {}; +// detect C.size() and C.data() template struct has_size_and_data : std::false_type {}; @@ -89,6 +103,7 @@ struct has_size_and_data())), decltype(std::data(std::declval()))>> : std::true_type {}; +// detect if *(C.data()) is compatible with E template struct is_compatible_element : std::false_type {}; @@ -98,16 +113,22 @@ struct is_compatible_element()))>::type (*)[], E (*)[]> {}; +// A container C: +// - is not any of [span, c-array, std::array] +// - has C.size() and C.data() interface template struct is_container : std::bool_constant::value && !is_array::value && !is_std_array::value && has_size_and_data::value> {}; +// detect if C is a container and is compatible with element E template struct is_compatible_container : std::bool_constant::value && is_compatible_element::value> {}; -template +// detect if T is a complete type +// NB: sizeof(T) cannot be used with incomplete types +template struct is_complete : std::false_type {}; template @@ -158,7 +179,9 @@ class span { // - size() == count // explicit: extent != dynamic_extent // NB: iterator concepts to further restrict overload resolution - constexpr span(pointer ptr, size_type count) : storage_(ptr, count) {} + constexpr span(pointer ptr, size_type count) : storage_(ptr, count) { + span_EXPECTS((ptr == nullptr && count == 0) || (ptr != nullptr && count >= 0)); + } // constructs a span that is a view over the range [first, last) // - data() == std::to_address(first) @@ -166,7 +189,9 @@ class span { // explicit: extent != dynamic_extent // NB: iterator concepts to further restrict overload resolution constexpr span(pointer first_elem, pointer last_elem) - : storage_(first_elem, last_elem - first_elem) {} + : storage_(first_elem, last_elem - first_elem) { + span_EXPECTS(last_elem - first_elem >= 0); + } // constructs a span that is a view over arr // - data() == std::to_data(arr) @@ -224,7 +249,9 @@ class span { std::is_convertible::value, int>::type = 0> constexpr span(const span &other) noexcept - : storage_(other.data(), other.size()) {} + : storage_(other.data(), other.size()) { + span_EXPECTS(OtherExtent == dynamic_extent || other.size() == OtherExtent); + } ~span() noexcept = default; @@ -235,10 +262,12 @@ class span { // https://en.cppreference.com/w/cpp/container/span/first template constexpr span first() const { + span_EXPECTS(Count >= 0 && Count <= size()); return {data(), Count}; } constexpr span first(size_type count) const { + span_EXPECTS(count >= 0 && count <= size()); return {data(), count}; } @@ -246,10 +275,12 @@ class span { // https://en.cppreference.com/w/cpp/container/span/last template constexpr span last() const { + span_EXPECTS(Count >= 0 && Count <= size()); return {data() + (size() - Count), Count}; } constexpr span last(size_type count) const { + span_EXPECTS(count >= 0 && count <= size()); return {data() + (size() - count), count}; } @@ -264,11 +295,17 @@ class span { template constexpr subspan_return_t subspan() const { + span_EXPECTS((Offset >= 0 && Offset <= size()) && + (Count == dynamic_extent || (Count >= 0 && Count + Offset <= size()))); return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset}; } constexpr span subspan(size_type offset, size_type count = dynamic_extent) const { + span_EXPECTS((offset >= 0 && offset <= size()) && + (count == static_cast(dynamic_extent) || + (count >= 0 && count + offset <= size()))); + return {data() + offset, count == dynamic_extent ? size() - offset : count}; } // --[span.subviews] @@ -289,11 +326,20 @@ class span { // --[span.observers] // [span.element_access]-- - constexpr reference operator[](size_type idx) const { return *(data() + idx); } + constexpr reference operator[](size_type idx) const { + span_EXPECTS(idx >= 0 && idx < size()); + return *(data() + idx); + } - constexpr reference front() const { return *data(); } + constexpr reference front() const { + span_EXPECTS(!empty()); + return *data(); + } - constexpr reference back() const { return *(data() + (size() - 1)); } + constexpr reference back() const { + span_EXPECTS(!empty()); + return *(data() + (size() - 1)); + } constexpr pointer data() const noexcept { return storage_.ptr; } // --[span.element_access] @@ -406,4 +452,5 @@ class tuple_element> { } // end namespace std -#endif +#endif // __cpp_lib_span +#endif // PORTS_OF_CALL_SPAN_HH_ diff --git a/test/test_span.cpp b/test/test_span.cpp index c6e4256..71a9b4f 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -1,4 +1,4 @@ -#include "ports-of-call/span.hh" +#include "ports-of-call/span.hpp" #include #ifndef CATCH_CONFIG_FAST_COMPILE @@ -70,49 +70,55 @@ constexpr bool contains(span s, span sub) { } // namespace detail +// +// span testing +// +// The following tests for the correct construction of span and the expected +// results. +// + TEST_CASE("span default construction", "[PortsOfCall::span]") { static_assert(std::is_nothrow_default_constructible>::value, - "span is not nothrow default constructible"); + "Error: span is not nothrow default constructible"); static_assert(std::is_nothrow_constructible>::value, - "span is not nothrow constructible"); - + "Error: span is not nothrow constructible"); + // span 0> should not be noexecpt static_assert(!std::is_nothrow_constructible>::value, - "span is nothrow construcible"); - - SECTION("dynamic size") { - constexpr span static_span; - static_assert(nullptr == static_span.data(), ""); - static_assert(0u == static_span.size(), ""); - - static_assert(static_span.begin() == static_span.end(), - "default constructed dynamic size span has begin != end"); - } - - SECTION("fixed size") { - constexpr span s{}; - static_assert(s.size() == 0, "default constructed fixed size span has size != 0"); - static_assert(s.data() == 0, - "default constructed fixed size span has data != nullptr"); - static_assert(s.begin() == s.end(), - "default constructed fixed size span has begin != end"); - } + "Error: span is nothrow construcible"); + + constexpr span dynamic_span; + static_assert(nullptr == dynamic_span.data(), + "Error: default constructed dynamic size span has data != nullptr"); + static_assert(0u == dynamic_span.size(), + "Error: default constructed dynamic size span has size != 0"); + + static_assert(dynamic_span.begin() == dynamic_span.end(), + "Error: default constructed dynamic size span has begin != end"); + + constexpr span s{}; + static_assert(s.size() == 0u, + "Error: default constructed fixed size span has size != 0"); + static_assert(s.data() == nullptr, + "Error: default constructed fixed size span has data != nullptr"); + static_assert(s.begin() == s.end(), + "Error: default constructed fixed size span has begin != end"); } -TEST_CASE("span iter, extent construction", "[PortsOfCall::span]") { +TEST_CASE("span (iter, extent) construction", "[PortsOfCall::span]") { static_assert(std::is_constructible, int *, int>::value, - "span(int*, int) is not constructible"); + "Error: span(int*, int) should be constructible"); static_assert(std::is_constructible, int *, int>::value, - "span(int*, int) is not constructible"); + "Error: span(int*, int) is should be constructible"); static_assert(std::is_constructible, const int *, int>::value, - "span(const int*, int) is not constructible"); + "Error: span(const int*, int) should be constructible"); static_assert(std::is_constructible, int *, int>::value, - "span(int*, int) is not constructible"); + "Error: span(int*, int) should be constructible"); static_assert(std::is_constructible, int *, int>::value, - "span(int*, int) is not constructible"); + "Error: span(int*, int) should be constructible"); static_assert(std::is_constructible, const int *, int>::value, - "span(const int*, int) is not constructible"); + "Error: span(const int*, int) should be constructible"); int arr[] = {0, 1, 2}; @@ -133,8 +139,9 @@ TEST_CASE("span iter, extent construction", "[PortsOfCall::span]") { } } -TEST_CASE("span Carr construction", "[PortsOfCall::span]") { - using int_carr_t = int[3]; +TEST_CASE("span c-array construction", "[PortsOfCall::span]") { + using int_carr_t = int[3]; // for l-value int[3] + static_assert(std::is_nothrow_constructible, int_carr_t &>::value, ""); static_assert(!std::is_constructible, const int_carr_t &>::value, ""); static_assert(std::is_nothrow_constructible, int_carr_t &>::value, ""); @@ -175,23 +182,23 @@ TEST_CASE("span Carr construction", "[PortsOfCall::span]") { } SECTION("constexpr c-array") { - static constexpr int kArray[] = {5, 4, 3, 2, 1}; - constexpr span dynamic_span(kArray); - static_assert(kArray == dynamic_span.data(), ""); - static_assert(std::size(kArray) == dynamic_span.size(), ""); - static_assert(kArray[0] == dynamic_span[0], ""); - static_assert(kArray[1] == dynamic_span[1], ""); - static_assert(kArray[2] == dynamic_span[2], ""); - static_assert(kArray[3] == dynamic_span[3], ""); - static_assert(kArray[4] == dynamic_span[4], ""); - constexpr span static_span(kArray); - static_assert(kArray == static_span.data(), ""); - static_assert(std::size(kArray) == static_span.size(), ""); - static_assert(kArray[0] == static_span[0], ""); - static_assert(kArray[1] == static_span[1], ""); - static_assert(kArray[2] == static_span[2], ""); - static_assert(kArray[3] == static_span[3], ""); - static_assert(kArray[4] == static_span[4], ""); + static constexpr int arr[] = {5, 4, 3, 2, 1}; + constexpr span dynamic_span(arr); + static_assert(arr == dynamic_span.data(), ""); + static_assert(std::size(arr) == dynamic_span.size(), ""); + static_assert(arr[0] == dynamic_span[0], ""); + static_assert(arr[1] == dynamic_span[1], ""); + static_assert(arr[2] == dynamic_span[2], ""); + static_assert(arr[3] == dynamic_span[3], ""); + static_assert(arr[4] == dynamic_span[4], ""); + constexpr span static_span(arr); + static_assert(arr == static_span.data(), ""); + static_assert(std::size(arr) == static_span.size(), ""); + static_assert(arr[0] == static_span[0], ""); + static_assert(arr[1] == static_span[1], ""); + static_assert(arr[2] == static_span[2], ""); + static_assert(arr[3] == static_span[3], ""); + static_assert(arr[4] == static_span[4], ""); } } @@ -200,7 +207,7 @@ TEST_CASE("span std::array construction", "[PortsOfCall::span]") { // for non-void types is equivalent to checking whether the following // expression is well-formed: // - // T obj = std::declval(); + // To obj = std::declval(); // // In particular we are checking whether From is implicitly convertible to To, // which also implies that To is explicitly constructible from From. @@ -301,23 +308,18 @@ TEST_CASE("span container construction", "[PortsOfCall::span]") { static_assert(!std::is_constructible, vec_t &>::value, ""); static_assert(!std::is_constructible, const vec_t &>::value, ""); - vec_t varr = {1, 2, 3}; + vec_t array = {1, 2, 3}; - SECTION("dynamic") { - span s{varr}; - REQUIRE(s.size() == 3); - REQUIRE(s.data() == varr.data()); - REQUIRE(s.begin() == varr.data()); - REQUIRE(s.end() == varr.data() + 3); - } - - SECTION("dynamic const") { - span s{varr}; - REQUIRE(s.size() == 3); - REQUIRE(s.data() == varr.data()); - REQUIRE(s.begin() == varr.data()); - REQUIRE(s.end() == varr.data() + 3); - } + span const_span(array); + REQUIRE(array.data() == const_span.data()); + REQUIRE(array.size() == const_span.size()); + for (size_t i = 0; i < const_span.size(); ++i) + REQUIRE(array[i] == const_span[i]); + span dynamic_span(array); + REQUIRE(array.data() == dynamic_span.data()); + REQUIRE(array.size() == dynamic_span.size()); + for (size_t i = 0; i < dynamic_span.size(); ++i) + REQUIRE(array[i] == dynamic_span[i]); // NB: fixed/fixed const is equivilant to std::array construction } @@ -332,16 +334,32 @@ TEST_CASE("span different size span construction", "PortsOfCall::span") { using dynamic_const_span = span; // - static_assert(std::is_trivially_copyable::value, ""); - static_assert(std::is_trivially_move_constructible::value, ""); - - static_assert(!std::is_constructible::value, ""); - - static_assert(!std::is_constructible::value, ""); - static_assert(!std::is_constructible::value, ""); - static_assert(!std::is_constructible::value, ""); + static_assert(std::is_trivially_copyable::value, + "Error: span with zero extent should be " + "trivially copayable"); + static_assert(std::is_trivially_move_constructible::value, + "Error: span with zero extent should be " + "trivially move constructible"); + + static_assert(!std::is_constructible::value, + "Error: span with zero extent should not be " + "constructible from span with extent > 0"); + + static_assert(!std::is_constructible::value, + "Error: span with zero extent should not be " + "constructible from span"); + static_assert(!std::is_constructible::value, + "Error: span with zero extent should not be " + "constructible from span with extent > 0"); + + static_assert(!std::is_constructible::value, + "Error: span with zero extent should not be " + "constructible from span with dynamic extent"); + + static_assert(std::is_nothrow_constructible::value, + "Error: span with zero extent should be nothrow " + "constructible from span with dynamic extent"); - static_assert(std::is_nothrow_constructible::value, ""); // static_assert(std::is_trivially_copyable::value, ""); static_assert(std::is_trivially_move_constructible::value, ""); @@ -397,10 +415,11 @@ TEST_CASE("span different size span construction", "PortsOfCall::span") { ""); static_assert(std::is_nothrow_constructible::value, ""); + constexpr zero_const_span s0{}; constexpr dynamic_const_span d{s0}; - static_assert(d.size() == 0, ""); + static_assert(d.size() == 0u, ""); static_assert(d.data() == nullptr, ""); static_assert(d.begin() == d.end(), ""); } @@ -473,12 +492,18 @@ TEST_CASE("span member subview operations", "[PortsOfCall::span]") { TEST_CASE("span observers", "[PortsOfCall::span]") { constexpr span empty{}; - static_assert(empty.size() == 0, ""); - static_assert(empty.empty(), ""); + static_assert( + empty.size() == 0u, + "Error: span.size() should be constexpr zero for zero-sized constexpr span"); + static_assert( + empty.empty(), + "Error: span.empty() should be constexpr true for zero-sized constexpr span"); constexpr int arr[] = {1, 2, 3}; - static_assert(span{arr}.size() == 3, ""); - static_assert(!span{arr}.empty(), ""); + static_assert(span{arr}.size() == 3, + "Error: span.size() should be constepxr 3"); + static_assert(!span{arr}.empty(), + "Error: span.empty() should be constexpr false"); } TEST_CASE("span element access", "[PortsOfCall::span]") { @@ -491,6 +516,8 @@ TEST_CASE("span element access", "[PortsOfCall::span]") { } // adopted from https://en.cppreference.com/w/cpp/container/span +// a test that exercises various capabilities of span, both in +// constexpr contexts and at runtime. TEST_CASE("span typical", "[PortsOfCall::span]") { constexpr int a[]{0, 1, 2, 3, 4, 5, 6, 7, 8}; constexpr int b[]{8, 7, 6}; From cf68b34c94b1b376af0f08d352898df98af6e34c Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Thu, 10 Oct 2024 10:28:05 -0600 Subject: [PATCH 07/12] copyright --- ports-of-call/span.hpp | 93 +++++++++++++++++++++++++++++------------- test/test_span.cpp | 17 +++++++- 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/ports-of-call/span.hpp b/ports-of-call/span.hpp index 32f6da3..3cfd07a 100644 --- a/ports-of-call/span.hpp +++ b/ports-of-call/span.hpp @@ -1,12 +1,26 @@ #ifndef PORTS_OF_CALL_SPAN_HH_ #define PORTS_OF_CALL_SPAN_HH_ +// ======================================================================================== +// © (or copyright) 2019-2024. Triad National Security, LLC. All rights +// reserved. This program was produced under U.S. Government contract +// 89233218CNA000001 for Los Alamos National Laboratory (LANL), which is +// operated by Triad National Security, LLC for the U.S. Department of +// Energy/National Nuclear Security Administration. All rights in the +// program are reserved by Triad National Security, LLC, and the +// U.S. Department of Energy/National Nuclear Security +// Administration. The Government is granted for itself and others acting +// on its behalf a nonexclusive, paid-up, irrevocable worldwide license +// in this material to reproduce, prepare derivative works, distribute +// copies to the public, perform publicly and display publicly, and to +// permit others to do so. +// ======================================================================================== + #include #include #include #include #include -#include #include #if defined(__cpp_lib_span) // do we already have std::span? @@ -20,6 +34,8 @@ using std::span; #else +#define span_REQUIRES(...) typename std::enable_if<((__VA_ARGS__)), int>::type = 0 + #define span_EXPECTS(...) assert((__VA_ARGS__)) namespace PortsOfCall::span { @@ -171,6 +187,7 @@ class span { // - data() == nullptr // - size() == 0 template typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0> constexpr span() noexcept {} @@ -197,24 +214,34 @@ class span { // - data() == std::to_data(arr) // - size() == N template ::value, - int>::type = 0> + span_REQUIRES( + (E == dynamic_extent || N == E) && + detail::is_compatible_container::value)> + // typename std::enable_if<(E == dynamic_extent || N == E) && + // detail::is_compatible_container< + // element_type (&)[N], ElementType>::value, + // int>::type = 0> constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) {} template &, ElementType>::value, - int>::type = 0> + span_REQUIRES( + (E == dynamic_extent || N == E) && + detail::is_compatible_container &, ElementType>::value)> + + // typename std::enable_if<(E == dynamic_extent || N == E) && + // detail::is_compatible_container< + // std::array &, ElementType>::value, + // int>::type = 0> constexpr span(std::array &arr) noexcept : storage_(arr.data(), N) {} template &, ElementType>::value, - int>::type = 0> + span_REQUIRES((E == dynamic_extent || N == E) && + detail::is_compatible_container &, + ElementType>::value)> + // typename std::enable_if<(E == dynamic_extent || N == E) && + // detail::is_compatible_container< + // const std::array &, ElementType>::value, + // int>::type = 0> constexpr span(const std::array &arr) noexcept : storage_(arr.data(), N) {} // constructs a span that is a view of cont @@ -223,31 +250,40 @@ class span { // NB: C++20 replaces Container concept with Range concept. // NB: CompatibleContainer(or future Range) concept is exclusive with C-array, // std::array, and span - template ::value && - detail::is_compatible_container::value, - int>::type = 0> + template < + class Container, std::size_t E = Extent, + span_REQUIRES(E == dynamic_extent && detail::is_container::value && + detail::is_compatible_container::value)> + // typename std::enable_if< + // E == dynamic_extent && detail::is_container::value && + // detail::is_compatible_container::value, + // int>::type = 0> constexpr span(Container &cont) noexcept : storage_(detail::data(cont), detail::size(cont)) {} - template < - class Container, std::size_t E = Extent, - typename std::enable_if< - E == dynamic_extent && detail::is_container::value && - detail::is_compatible_container::value, - int>::type = 0> + template ::value && + detail::is_compatible_container::value)> + // typename std::enable_if< + // E == dynamic_extent && detail::is_container::value && + // detail::is_compatible_container::value, + // int>::type = 0> constexpr span(const Container &cont) noexcept : storage_(detail::data(cont), detail::size(cont)) {} constexpr span(const span &other) noexcept = default; template ::value, - int>::type = 0> + std::is_convertible::value)> + // typename std::enable_if< + // (Extent == dynamic_extent || OtherExtent == dynamic_extent || + // Extent == OtherExtent) && + // std::is_convertible::value, + // int>::type = 0> constexpr span(const span &other) noexcept : storage_(other.data(), other.size()) { span_EXPECTS(OtherExtent == dynamic_extent || other.size() == OtherExtent); @@ -419,7 +455,8 @@ as_bytes(span s) noexcept { } template ::value, int>::type = 0> + span_REQUIRES(!std::is_const::value)> +// typename std::enable_if::value, int>::type = 0> span as_writable_bytes(span s) noexcept { return {reinterpret_cast(s.data()), s.size_bytes()}; diff --git a/test/test_span.cpp b/test/test_span.cpp index 71a9b4f..86d9759 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -1,13 +1,26 @@ #include "ports-of-call/span.hpp" #include +// ======================================================================================== +// © (or copyright) 2019-2024. Triad National Security, LLC. All rights +// reserved. This program was produced under U.S. Government contract +// 89233218CNA000001 for Los Alamos National Laboratory (LANL), which is +// operated by Triad National Security, LLC for the U.S. Department of +// Energy/National Nuclear Security Administration. All rights in the +// program are reserved by Triad National Security, LLC, and the +// U.S. Department of Energy/National Nuclear Security +// Administration. The Government is granted for itself and others acting +// on its behalf a nonexclusive, paid-up, irrevocable worldwide license +// in this material to reproduce, prepare derivative works, distribute +// copies to the public, perform publicly and display publicly, and to +// permit others to do so. +// ======================================================================================== + #ifndef CATCH_CONFIG_FAST_COMPILE #define CATCH_CONFIG_FAST_COMPILE #include #endif -#include - namespace test { using namespace PortsOfCall::span; From 9dd8a6a690c5a1784d938615f1d25eec5c8fc5f7 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Thu, 10 Oct 2024 10:35:28 -0600 Subject: [PATCH 08/12] removed commented code --- ports-of-call/span.hpp | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/ports-of-call/span.hpp b/ports-of-call/span.hpp index 3cfd07a..39f5d9d 100644 --- a/ports-of-call/span.hpp +++ b/ports-of-call/span.hpp @@ -217,31 +217,18 @@ class span { span_REQUIRES( (E == dynamic_extent || N == E) && detail::is_compatible_container::value)> - // typename std::enable_if<(E == dynamic_extent || N == E) && - // detail::is_compatible_container< - // element_type (&)[N], ElementType>::value, - // int>::type = 0> constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) {} template &, ElementType>::value)> - - // typename std::enable_if<(E == dynamic_extent || N == E) && - // detail::is_compatible_container< - // std::array &, ElementType>::value, - // int>::type = 0> constexpr span(std::array &arr) noexcept : storage_(arr.data(), N) {} template &, ElementType>::value)> - // typename std::enable_if<(E == dynamic_extent || N == E) && - // detail::is_compatible_container< - // const std::array &, ElementType>::value, - // int>::type = 0> constexpr span(const std::array &arr) noexcept : storage_(arr.data(), N) {} // constructs a span that is a view of cont @@ -254,10 +241,6 @@ class span { class Container, std::size_t E = Extent, span_REQUIRES(E == dynamic_extent && detail::is_container::value && detail::is_compatible_container::value)> - // typename std::enable_if< - // E == dynamic_extent && detail::is_container::value && - // detail::is_compatible_container::value, - // int>::type = 0> constexpr span(Container &cont) noexcept : storage_(detail::data(cont), detail::size(cont)) {} @@ -265,10 +248,6 @@ class span { span_REQUIRES( E == dynamic_extent && detail::is_container::value && detail::is_compatible_container::value)> - // typename std::enable_if< - // E == dynamic_extent && detail::is_container::value && - // detail::is_compatible_container::value, - // int>::type = 0> constexpr span(const Container &cont) noexcept : storage_(detail::data(cont), detail::size(cont)) {} @@ -279,11 +258,6 @@ class span { (Extent == dynamic_extent || OtherExtent == dynamic_extent || Extent == OtherExtent) && std::is_convertible::value)> - // typename std::enable_if< - // (Extent == dynamic_extent || OtherExtent == dynamic_extent || - // Extent == OtherExtent) && - // std::is_convertible::value, - // int>::type = 0> constexpr span(const span &other) noexcept : storage_(other.data(), other.size()) { span_EXPECTS(OtherExtent == dynamic_extent || other.size() == OtherExtent); @@ -456,7 +430,6 @@ as_bytes(span s) noexcept { template ::value)> -// typename std::enable_if::value, int>::type = 0> span as_writable_bytes(span s) noexcept { return {reinterpret_cast(s.data()), s.size_bytes()}; From dc731222151f01a5731fc18d395646c97c42c921 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Thu, 10 Oct 2024 10:40:13 -0600 Subject: [PATCH 09/12] removed junk code/comment --- ports-of-call/span.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ports-of-call/span.hpp b/ports-of-call/span.hpp index 39f5d9d..e952a6d 100644 --- a/ports-of-call/span.hpp +++ b/ports-of-call/span.hpp @@ -186,9 +186,7 @@ class span { // constructs an empty span // - data() == nullptr // - size() == 0 - template - typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0> + template constexpr span() noexcept {} // constructs a span that is a view over the range [first, first+count) From d8970a89c9fa9a1c2ac205a78d9166a5432ef500 Mon Sep 17 00:00:00 2001 From: Christopher Michael Mauney Date: Mon, 14 Oct 2024 13:44:12 -0600 Subject: [PATCH 10/12] portability test --- test/test_span.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/test_span.cpp b/test/test_span.cpp index 86d9759..a6f4564 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -1,5 +1,7 @@ #include "ports-of-call/span.hpp" +#include "ports-of-call/portability.hpp" #include +#include // ======================================================================================== // © (or copyright) 2019-2024. Triad National Security, LLC. All rights @@ -555,4 +557,42 @@ TEST_CASE("span typical", "[PortsOfCall::span]") { !detail::contains(span{a, 8}, span{a, 9})); } +// test span in portability context +TEST_CASE("span portability", "[PortsOfCall::span]") { + // extent + constexpr const std::size_t N = 1024; + constexpr const std::size_t Nb = N * sizeof(Real); + constexpr const Real tol = 1.0E-8; + + constexpr const Real pi = std::acos(-1); + + constexpr Real d_gp = 2. * pi / static_cast(N); + + std::vector h_gp(N); + for(auto i = 0; i < N; ++i) + { + h_gp[i] = -pi + static_cast(i) * d_gp; + } + + + Real *p_a = (Real*) PORTABLE_MALLOC(Nb); + + // device span, host span + span d_s(p_a, N); + span h_s(h_gp); + + // copy using spans + portableCopyToDevice(d_s.data(), h_s.data(), h_s.size_bytes()); + + // integrate cos x dx over [-pi,pi] + Real res_sum{0.0}; + portableReduce( "integrate", 0, N, PORTABLE_LAMBDA(const int i, Real& rsum){ rsum += (std::cos(d_s[i])*d_gp) ;}, res_sum); + + // require sum < tol + REQUIRE(std::abs(res_sum) < tol); + + + PORTABLE_FREE(p_a); +} + } // namespace test From 8b3617e9b99e57c3c2c8e3af67250a2542b33733 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Mon, 14 Oct 2024 17:35:11 -0600 Subject: [PATCH 11/12] docs updated --- doc/sphinx/src/using.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/sphinx/src/using.rst b/doc/sphinx/src/using.rst index 40a9d7a..60d9660 100644 --- a/doc/sphinx/src/using.rst +++ b/doc/sphinx/src/using.rst @@ -230,3 +230,24 @@ not yet ``constexpr``, so even with the "relaxed ``constexpr``" compilation mode not feature-complete on GPUs. This will change when those member functions become ``constexpr`` in C++20. +span.hpp + + +``PortsOfCall::span`` is implements ``std::span`` for C++17 (uses native implmentation in C++20) +as a view over contiguous data. ``span`` may have compile-time static extent, or a dynamic extent. +``span`` provides iterator functions similar to containers. + +.. code-block:: cpp + + int arr[] = {1, 2, 3}; + auto s = span{arr}; + for(auto & i : s) + { + i -= 1; + } + +``span::subspan`` returns a span over a subrange. Element access uses ``span::operator[]``. For +more information, see `C++ reference page `_. + + + From 761d580b981b51f3e3062ab5164575be6de0eed0 Mon Sep 17 00:00:00 2001 From: Christopher Mauney Date: Wed, 16 Oct 2024 13:14:37 -0600 Subject: [PATCH 12/12] edits --- ports-of-call/span.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ports-of-call/span.hpp b/ports-of-call/span.hpp index e952a6d..f8134f1 100644 --- a/ports-of-call/span.hpp +++ b/ports-of-call/span.hpp @@ -238,16 +238,18 @@ class span { template < class Container, std::size_t E = Extent, span_REQUIRES(E == dynamic_extent && detail::is_container::value && - detail::is_compatible_container::value)> - constexpr span(Container &cont) noexcept + detail::is_compatible_container::value && + !std::is_rvalue_reference::value)> + constexpr span(Container &&cont) noexcept : storage_(detail::data(cont), detail::size(cont)) {} - template ::value && - detail::is_compatible_container::value)> - constexpr span(const Container &cont) noexcept - : storage_(detail::data(cont), detail::size(cont)) {} + // template ::value && + // detail::is_compatible_container::value)> + // constexpr span(const Container &cont) noexcept + // : storage_(detail::data(cont), detail::size(cont)) {} constexpr span(const span &other) noexcept = default;