From 2eb32b200a523ab7e62a6e67165692364eaeece5 Mon Sep 17 00:00:00 2001 From: Jack Diver Date: Thu, 10 Oct 2019 15:25:17 +0100 Subject: [PATCH] Initial commit of refactored multi_span originating from the Microsoft GSL --- .bazelrc | 3 + .gitignore | 3 + BUILD | 9 + WORKSPACE | 10 + include/stdex/multi_span | 22 + include/stdex/multi_span.hpp | 2563 +++++++++++++++++++++++++++++++ test/BUILD | 63 + test/src/bounds_tests.cpp | 115 ++ test/src/multi_span_tests.cpp | 1720 +++++++++++++++++++++ test/src/strided_span_tests.cpp | 635 ++++++++ test/src/tests.cpp | 3 + test/unix_makefile.mk | 42 + third_party/BUILD | 0 third_party/BUILD.catch2 | 11 + 14 files changed, 5199 insertions(+) create mode 100644 .bazelrc create mode 100644 .gitignore create mode 100644 BUILD create mode 100644 WORKSPACE create mode 100644 include/stdex/multi_span create mode 100644 include/stdex/multi_span.hpp create mode 100644 test/BUILD create mode 100644 test/src/bounds_tests.cpp create mode 100644 test/src/multi_span_tests.cpp create mode 100644 test/src/strided_span_tests.cpp create mode 100644 test/src/tests.cpp create mode 100644 test/unix_makefile.mk create mode 100644 third_party/BUILD create mode 100644 third_party/BUILD.catch2 diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..668ead4 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,3 @@ +build --symlink_prefix=bazel-build/ +build --cxxopt='-std=c++14' + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b12fae1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +bazel-out +bazel-build diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..2a88a6d --- /dev/null +++ b/BUILD @@ -0,0 +1,9 @@ +cc_library( + name = "multi_span", + hdrs = glob(["include/**/*"]), + include_prefix = "stdex", + strip_include_prefix = "include/stdex", + visibility = [ + "//visibility:public", + ], +) diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..5b7cc34 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,10 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# Keep aligned with tests/CMakeLists.txt +http_archive( + name = "catch2", + build_file = "//third_party:BUILD.catch2", + sha256 = "5f31b93712e65d363f257ad0f0c02cfbed7a3988979d5f320ad7771e513d4cc8", + strip_prefix = "Catch2-2.0.1", + urls = ["https://github.com/catchorg/Catch2/archive/v2.0.1.tar.gz"], +) diff --git a/include/stdex/multi_span b/include/stdex/multi_span new file mode 100644 index 0000000..128f0f3 --- /dev/null +++ b/include/stdex/multi_span @@ -0,0 +1,22 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Maintained by Jack Diver. +// +// Modified from :- +// +// github.com/microsoft/GSL +// +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +/////////////////////////////////////////////////////////////////////////////// + +#include "multi_span.hpp" diff --git a/include/stdex/multi_span.hpp b/include/stdex/multi_span.hpp new file mode 100644 index 0000000..3933e2e --- /dev/null +++ b/include/stdex/multi_span.hpp @@ -0,0 +1,2563 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Maintained by Jack Diver. +// +// Modified from :- +// +// github.com/microsoft/GSL +// +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MULTI_SPAN_H +#define MULTI_SPAN_H + +#include // for transform, lexicographical_compare +#include // for array +#include // for ptrdiff_t, size_t, nullptr_t +#include // for PTRDIFF_MAX +#include // for divides, multiplies, minus, negate, plus +#include // for initializer_list +#include // for iterator, random_access_iterator_tag +#include // for numeric_limits +#include +#include +#include +#include +#include // for basic_string +#include // for enable_if_t, remove_cv_t, is_same, is_co... +#include + +#if defined(_MSC_VER) && !defined(__clang__) + +// turn off some warnings that are noisy about our Expects statements +#pragma warning(push) +// Turn MSVC /analyze rules that generate too much noise. TODO: fix in the tool. +// uninitalized member when constructor calls constructor +#pragma warning(disable : 26495) +// in some instantiations we cast to the same type +#pragma warning(disable : 26473) +// TODO: bug in parser - attributes and templates +#pragma warning(disable : 26490) +// TODO: bug - suppression does not work on +#pragma warning(disable : 26465) + +#if _MSC_VER < 1910 +#pragma push_macro("constexpr") +#define constexpr /*constexpr*/ +#endif // _MSC_VER < 1910 + +#endif // _MSC_VER + +// GCC 7 does not like the signed unsigned mismatch (size_t ptrdiff_t) +// While there is a conversion from signed to unsigned, it happens at +// compile time, so the compiler wouldn't have to warn indiscriminately, but +// could check if the source value actually doesn't fit into the target type +// and only warn in those cases. +#if defined(__GNUC__) && __GNUC__ > 6 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +namespace stdex +{ +// narrow_cast(): a searchable way to do narrowing casts of values +template +constexpr T narrow_cast(U&& u) noexcept +{ + return static_cast(std::forward(u)); +} + +/* +** begin definitions of index and bounds +*/ +namespace detail +{ +inline void EXPECTS(bool cond) +#if !defined(MULTI_SPAN_THROW_ON_CONTRACT_VIOLATION) + noexcept +#endif +{ + if (!cond) + { +#if defined(MULTI_SPAN_THROW_ON_CONTRACT_VIOLATION) +#define MULTI_SPAN_STRINGIFY_DETAIL(x) #x +#define MULTI_SPAN_STRINGIFY(x) MULTI_SPAN_STRINGIFY_DETAIL(x) + throw std::logic_error("stdex::multi_span: failure in " __FILE__ + " at line: " MULTI_SPAN_STRINGIFY(__LINE__)); +#undef MULTI_SPAN_STRINGIFY_DETAIL +#undef MULTI_SPAN_STRINGIFY +#elif defined(MULTI_SPAN_ASSERT_ON_CONTRACT_VIOLATION) + assert(cond); +#elif defined(MULTI_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) + std::terminate(); +#elif defined(MULTI_SPAN_UNENFORCED_ON_CONTRACT_VIOLATION) +#endif // MULTI_SPAN_THROW_ON_CONTRACT_VIOLATION + } +} + +template +struct SizeTypeTraits +{ + static const SizeType max_value = std::numeric_limits::max(); +}; + +template +class are_integral : public std::integral_constant +{ +}; + +template +class are_integral + : public std::integral_constant::value && + are_integral::value> +{ +}; +} // namespace detail + +template +class multi_span_index final +{ + static_assert(Rank > 0, "Rank must be greater than 0!"); + + template + friend class multi_span_index; + +public: + static const std::size_t rank = Rank; + using value_type = std::ptrdiff_t; + using size_type = value_type; + using reference = std::add_lvalue_reference_t; + using const_reference = + std::add_lvalue_reference_t>; + + constexpr multi_span_index() noexcept + { + } + + constexpr multi_span_index(const value_type (&values)[Rank]) noexcept + { + std::copy(values, values + Rank, elems); + } + + template ::value>> + constexpr multi_span_index(Ts... ds) noexcept + : elems{narrow_cast(ds)...} + { + } + + constexpr multi_span_index(const multi_span_index& other) noexcept = default; + + constexpr multi_span_index& + operator=(const multi_span_index& rhs) noexcept = default; + + // Preconditions: component_idx < rank + constexpr reference operator[](std::size_t component_idx) + { + detail::EXPECTS(component_idx < + Rank); // Component index must be less than rank + return elems[component_idx]; + } + + // Preconditions: component_idx < rank + constexpr const_reference operator[](std::size_t component_idx) const + { + detail::EXPECTS(component_idx < + Rank); // Component index must be less than rank + return elems[component_idx]; + } + + constexpr bool operator==(const multi_span_index& rhs) const + { + return std::equal(elems, elems + rank, rhs.elems); + } + + constexpr bool operator!=(const multi_span_index& rhs) const + { + return !(*this == rhs); + } + + constexpr multi_span_index operator+() const noexcept + { + return *this; + } + + constexpr multi_span_index operator-() const + { + multi_span_index ret = *this; + std::transform(ret, ret + rank, ret, std::negate{}); + return ret; + } + + constexpr multi_span_index operator+(const multi_span_index& rhs) const + { + multi_span_index ret = *this; + ret += rhs; + return ret; + } + + constexpr multi_span_index operator-(const multi_span_index& rhs) const + { + multi_span_index ret = *this; + ret -= rhs; + return ret; + } + + constexpr multi_span_index& operator+=(const multi_span_index& rhs) + { + std::transform( + elems, elems + rank, rhs.elems, elems, std::plus{}); + return *this; + } + + constexpr multi_span_index& operator-=(const multi_span_index& rhs) + { + std::transform( + elems, elems + rank, rhs.elems, elems, std::minus{}); + return *this; + } + + constexpr multi_span_index operator*(value_type v) const + { + multi_span_index ret = *this; + ret *= v; + return ret; + } + + constexpr multi_span_index operator/(value_type v) const + { + multi_span_index ret = *this; + ret /= v; + return ret; + } + + friend constexpr multi_span_index operator*(value_type v, + const multi_span_index& rhs) + { + return rhs * v; + } + + constexpr multi_span_index& operator*=(value_type v) + { + std::transform(elems, elems + rank, elems, [v](value_type x) { + return std::multiplies{}(x, v); + }); + return *this; + } + + constexpr multi_span_index& operator/=(value_type v) + { + std::transform(elems, elems + rank, elems, [v](value_type x) { + return std::divides{}(x, v); + }); + return *this; + } + +private: + value_type elems[Rank] = {}; +}; + +#if !defined(_MSC_VER) || _MSC_VER >= 1910 + +struct static_bounds_dynamic_range_t +{ + template ::value>> + constexpr operator T() const noexcept + { + return narrow_cast(-1); + } +}; + +constexpr bool operator==(static_bounds_dynamic_range_t, + static_bounds_dynamic_range_t) noexcept +{ + return true; +} + +constexpr bool operator!=(static_bounds_dynamic_range_t, + static_bounds_dynamic_range_t) noexcept +{ + return false; +} + +template ::value>> +constexpr bool operator==(static_bounds_dynamic_range_t, T other) noexcept +{ + return narrow_cast(-1) == other; +} + +template ::value>> +constexpr bool operator==(T left, static_bounds_dynamic_range_t right) noexcept +{ + return right == left; +} + +template ::value>> +constexpr bool operator!=(static_bounds_dynamic_range_t, T other) noexcept +{ + return narrow_cast(-1) != other; +} + +template ::value>> +constexpr bool operator!=(T left, static_bounds_dynamic_range_t right) noexcept +{ + return right != left; +} + +constexpr static_bounds_dynamic_range_t dynamic_range{}; +#else +const std::ptrdiff_t dynamic_range = -1; +#endif + +struct generalized_mapping_tag +{ +}; +struct contiguous_mapping_tag : generalized_mapping_tag +{ +}; + +namespace detail +{ +template +struct LessThan +{ + static const bool value = Left < Right; +}; + +template +struct BoundsRanges +{ + using size_type = std::ptrdiff_t; + static const size_type Depth = 0; + static const size_type DynamicNum = 0; + static const size_type CurrentRange = 1; + static const size_type TotalSize = 1; + + // TODO : following signature is for work around VS bug + template + constexpr BoundsRanges(const OtherRange&, bool /* firstLevel */) + { + } + + constexpr BoundsRanges(const std::ptrdiff_t* const) + { + } + constexpr BoundsRanges() noexcept = default; + + template + constexpr void serialize(T&) const + { + } + + template + constexpr size_type linearize(const T&) const + { + return 0; + } + + template + constexpr size_type contains(const T&) const + { + return -1; + } + + constexpr size_type elementNum(std::size_t) const noexcept + { + return 0; + } + + constexpr size_type totalSize() const noexcept + { + return TotalSize; + } + + constexpr bool operator==(const BoundsRanges&) const noexcept + { + return true; + } +}; + +template +struct BoundsRanges : BoundsRanges +{ + using Base = BoundsRanges; + using size_type = std::ptrdiff_t; + static const std::size_t Depth = Base::Depth + 1; + static const std::size_t DynamicNum = Base::DynamicNum + 1; + static const size_type CurrentRange = dynamic_range; + static const size_type TotalSize = dynamic_range; + +private: + size_type m_bound; + +public: + constexpr BoundsRanges(const std::ptrdiff_t* const arr) + : Base(arr + 1), m_bound(*arr * this->Base::totalSize()) + { + detail::EXPECTS(0 <= *arr); + } + + constexpr BoundsRanges() noexcept : m_bound(0) + { + } + + template + constexpr BoundsRanges( + const BoundsRanges& other, + bool /* firstLevel */ = true) + : Base(static_cast&>(other), false) + , m_bound(other.totalSize()) + { + } + + template + constexpr void serialize(T& arr) const + { + arr[Dim] = elementNum(); + this->Base::template serialize(arr); + } + + template + constexpr size_type linearize(const T& arr) const + { + const size_type index = this->Base::totalSize() * arr[Dim]; + detail::EXPECTS(index < m_bound); + return index + this->Base::template linearize(arr); + } + + template + constexpr size_type contains(const T& arr) const + { + const ptrdiff_t last = this->Base::template contains(arr); + if (last == -1) + return -1; + const ptrdiff_t cur = this->Base::totalSize() * arr[Dim]; + return cur < m_bound ? cur + last : -1; + } + + constexpr size_type totalSize() const noexcept + { + return m_bound; + } + + constexpr size_type elementNum() const noexcept + { + return totalSize() / this->Base::totalSize(); + } + + constexpr size_type elementNum(std::size_t dim) const noexcept + { + if (dim > 0) + return this->Base::elementNum(dim - 1); + else + return elementNum(); + } + + constexpr bool operator==(const BoundsRanges& rhs) const noexcept + { + return m_bound == rhs.m_bound && + static_cast(*this) == static_cast(rhs); + } +}; + +template +struct BoundsRanges : BoundsRanges +{ + using Base = BoundsRanges; + using size_type = std::ptrdiff_t; + static const std::size_t Depth = Base::Depth + 1; + static const std::size_t DynamicNum = Base::DynamicNum; + static const size_type CurrentRange = CurRange; + static const size_type TotalSize = Base::TotalSize == dynamic_range + ? dynamic_range + : CurrentRange * Base::TotalSize; + + constexpr BoundsRanges(const std::ptrdiff_t* const arr) : Base(arr) + { + } + constexpr BoundsRanges() = default; + + template + constexpr BoundsRanges( + const BoundsRanges& other, + bool firstLevel = true) + : Base(static_cast&>(other), false) + { + (void)firstLevel; + } + + template + constexpr void serialize(T& arr) const + { + arr[Dim] = elementNum(); + this->Base::template serialize(arr); + } + + template + constexpr size_type linearize(const T& arr) const + { + // Index is out of range + detail::EXPECTS(arr[Dim] >= 0 && arr[Dim] < CurrentRange); + const ptrdiff_t d = arr[Dim]; + return this->Base::totalSize() * d + + this->Base::template linearize(arr); + } + + template + constexpr size_type contains(const T& arr) const + { + if (arr[Dim] >= CurrentRange) + return -1; + const size_type last = this->Base::template contains(arr); + if (last == -1) + return -1; + return this->Base::totalSize() * arr[Dim] + last; + } + + constexpr size_type totalSize() const noexcept + { + return CurrentRange * this->Base::totalSize(); + } + + constexpr size_type elementNum() const noexcept + { + return CurrentRange; + } + + constexpr size_type elementNum(std::size_t dim) const noexcept + { + if (dim > 0) + return this->Base::elementNum(dim - 1); + else + return elementNum(); + } + + constexpr bool operator==(const BoundsRanges& rhs) const noexcept + { + return static_cast(*this) == static_cast(rhs); + } +}; + +template +struct BoundsRangeConvertible + : public std::integral_constant< + bool, + (SourceType::TotalSize >= TargetType::TotalSize || + TargetType::TotalSize == dynamic_range || + SourceType::TotalSize == dynamic_range || TargetType::TotalSize == 0)> +{ +}; + +template +struct TypeListIndexer +{ + const TypeChain& obj_; + constexpr TypeListIndexer(const TypeChain& obj) : obj_(obj) + { + } + + template + constexpr const TypeChain& getObj(std::true_type) + { + return obj_; + } + + template + constexpr auto getObj(std::false_type) -> decltype( + TypeListIndexer(static_cast(obj_)).template get()) + { + return TypeListIndexer(static_cast(obj_)) + .template get(); + } + + template + constexpr auto get() + -> decltype(getObj(std::integral_constant())) + { + return getObj(std::integral_constant()); + } +}; + +template +constexpr TypeListIndexer createTypeListIndexer(const TypeChain& obj) +{ + return TypeListIndexer(obj); +} + +template 1), + typename Ret = std::enable_if_t>> +constexpr Ret shift_left(const multi_span_index& other) noexcept +{ + Ret ret{}; + for (std::size_t i = 0; i < Rank - 1; ++i) + { + ret[i] = other[i + 1]; + } + return ret; +} +} // namespace detail + +template +class bounds_iterator; + +template +class static_bounds +{ +public: + static_bounds(const detail::BoundsRanges&) + { + } +}; + +template +class static_bounds +{ + using MyRanges = detail::BoundsRanges; + + MyRanges m_ranges; + constexpr static_bounds(const MyRanges& range) noexcept : m_ranges(range) + { + } + + template + friend class static_bounds; + +public: + static const std::size_t rank = MyRanges::Depth; + static const std::size_t dynamic_rank = MyRanges::DynamicNum; + static const std::ptrdiff_t static_size = MyRanges::TotalSize; + + using size_type = std::ptrdiff_t; + using index_type = multi_span_index; + using const_index_type = std::add_const_t; + using iterator = bounds_iterator; + using const_iterator = bounds_iterator; + using difference_type = std::ptrdiff_t; + using sliced_type = static_bounds; + using mapping_type = contiguous_mapping_tag; + + constexpr static_bounds() /*noexcept*/ = default; + + template + struct BoundsRangeConvertible2; + + template > + static auto helpBoundsRangeConvertible(SourceType, TargetType, std::true_type) + -> Ret; + + template + static auto helpBoundsRangeConvertible(SourceType, TargetType, ...) + -> std::false_type; + + template + struct BoundsRangeConvertible2 + : decltype(helpBoundsRangeConvertible( + SourceType(), + TargetType(), + std::integral_constant< + bool, + SourceType::Depth == TargetType::Depth && + (SourceType::CurrentRange == TargetType::CurrentRange || + TargetType::CurrentRange == dynamic_range || + SourceType::CurrentRange == dynamic_range)>())) + { + }; + + template + struct BoundsRangeConvertible2 : std::true_type + { + }; + + template + struct BoundsRangeConvertible + : decltype(helpBoundsRangeConvertible( + SourceType(), + TargetType(), + std::integral_constant< + bool, + SourceType::Depth == TargetType::Depth && + (!detail::LessThan::value || + TargetType::CurrentRange == dynamic_range || + SourceType::CurrentRange == dynamic_range)>())) + { + }; + + template + struct BoundsRangeConvertible : std::true_type + { + }; + + template , + detail::BoundsRanges>::value>> + constexpr static_bounds(const static_bounds& other) + : m_ranges(other.m_ranges) + { + detail::EXPECTS((MyRanges::DynamicNum == 0 && + detail::BoundsRanges::DynamicNum == 0) || + MyRanges::DynamicNum > 0 || + other.m_ranges.totalSize() >= m_ranges.totalSize()); + } + + constexpr static_bounds(std::initializer_list il) + : m_ranges(il.begin()) + { + // Size of the initializer list must match the rank of the array + detail::EXPECTS((MyRanges::DynamicNum == 0 && il.size() == 1 && + *il.begin() == static_size) || + MyRanges::DynamicNum == il.size()); + // Size of the range must be less than the max element of the size type + detail::EXPECTS(m_ranges.totalSize() <= PTRDIFF_MAX); + } + + constexpr sliced_type slice() const noexcept + { + return sliced_type{ + static_cast&>(m_ranges)}; + } + + constexpr size_type stride() const noexcept + { + return rank > 1 ? slice().size() : 1; + } + + constexpr size_type size() const noexcept + { + return m_ranges.totalSize(); + } + + constexpr size_type total_size() const noexcept + { + return m_ranges.totalSize(); + } + + constexpr size_type linearize(const index_type& idx) const + { + return m_ranges.linearize(idx); + } + + constexpr bool contains(const index_type& idx) const noexcept + { + return m_ranges.contains(idx) != -1; + } + + constexpr size_type operator[](std::size_t idx) const noexcept + { + return m_ranges.elementNum(idx); + } + + template + constexpr size_type extent() const noexcept + { + static_assert( + Dim < rank, + "dimension should be less than rank (dimension count starts from 0)"); + return detail::createTypeListIndexer(m_ranges) + .template get() + .elementNum(); + } + + template + constexpr size_type extent(IntType dim) const + { + static_assert(std::is_integral::value, + "Dimension parameter must be supplied as an integral type."); + auto real_dim = narrow_cast(dim); + detail::EXPECTS(real_dim < rank); + + return m_ranges.elementNum(real_dim); + } + + constexpr index_type index_bounds() const noexcept + { + size_type extents[rank] = {}; + m_ranges.serialize(extents); + return {extents}; + } + + template + constexpr bool operator==(const static_bounds& rhs) const noexcept + { + return this->size() == rhs.size(); + } + + template + constexpr bool operator!=(const static_bounds& rhs) const noexcept + { + return !(*this == rhs); + } + + constexpr const_iterator begin() const noexcept + { + return const_iterator(*this, index_type{}); + } + + constexpr const_iterator end() const noexcept + { + return const_iterator(*this, this->index_bounds()); + } +}; + +template +class strided_bounds +{ + template + friend class strided_bounds; + +public: + static const std::size_t rank = Rank; + using value_type = std::ptrdiff_t; + using reference = std::add_lvalue_reference_t; + using const_reference = std::add_const_t; + using size_type = value_type; + using difference_type = value_type; + using index_type = multi_span_index; + using const_index_type = std::add_const_t; + using iterator = bounds_iterator; + using const_iterator = bounds_iterator; + static const value_type dynamic_rank = rank; + static const value_type static_size = dynamic_range; + using sliced_type = + std::conditional_t, void>; + using mapping_type = generalized_mapping_tag; + + constexpr strided_bounds(const strided_bounds&) noexcept = default; + + constexpr strided_bounds& operator=(const strided_bounds&) noexcept = default; + + constexpr strided_bounds(const value_type (&values)[rank], index_type strides) + : m_extents(values), m_strides(std::move(strides)) + { + } + + constexpr strided_bounds(const index_type& extents, + const index_type& strides) noexcept + : m_extents(extents), m_strides(strides) + { + } + + constexpr index_type strides() const noexcept + { + return m_strides; + } + + constexpr size_type total_size() const noexcept + { + size_type ret = 0; + for (std::size_t i = 0; i < rank; ++i) + { + ret += (m_extents[i] - 1) * m_strides[i]; + } + return ret + 1; + } + + constexpr size_type size() const noexcept + { + size_type ret = 1; + for (std::size_t i = 0; i < rank; ++i) + { + ret *= m_extents[i]; + } + return ret; + } + + constexpr bool contains(const index_type& idx) const noexcept + { + for (std::size_t i = 0; i < rank; ++i) + { + if (idx[i] < 0 || idx[i] >= m_extents[i]) + return false; + } + return true; + } + + constexpr size_type linearize(const index_type& idx) const + { + size_type ret = 0; + for (std::size_t i = 0; i < rank; i++) + { + detail::EXPECTS(idx[i] < + m_extents[i]); // index is out of bounds of the array + ret += idx[i] * m_strides[i]; + } + return ret; + } + + constexpr size_type stride() const noexcept + { + return m_strides[0]; + } + + template 1), + typename Ret = std::enable_if_t> + constexpr sliced_type slice() const + { + return {detail::shift_left(m_extents), detail::shift_left(m_strides)}; + } + + template + + constexpr size_type extent() const noexcept + { + static_assert( + Dim < Rank, + "dimension should be less than rank (dimension count starts from 0)"); + return m_extents[Dim]; + } + + constexpr index_type index_bounds() const noexcept + { + return m_extents; + } + + constexpr const_iterator begin() const noexcept + { + return const_iterator{*this, index_type{}}; + } + + constexpr const_iterator end() const noexcept + { + return const_iterator{*this, index_bounds()}; + } + +private: + index_type m_extents; + index_type m_strides; +}; + +template +struct is_bounds : std::integral_constant +{ +}; +template +struct is_bounds> : std::integral_constant +{ +}; +template +struct is_bounds> : std::integral_constant +{ +}; + +template +class bounds_iterator +{ +public: + static const std::size_t rank = IndexType::rank; + using iterator_category = std::random_access_iterator_tag; + using value_type = IndexType; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using index_type = value_type; + using index_size_type = typename IndexType::value_type; + template + explicit bounds_iterator(const Bounds& bnd, value_type curr) noexcept + : boundary_(bnd.index_bounds()), curr_(std::move(curr)) + { + static_assert(is_bounds::value, "Bounds type must be provided"); + } + + constexpr reference operator*() const noexcept + { + return curr_; + } + + constexpr pointer operator->() const noexcept + { + return &curr_; + } + + constexpr bounds_iterator& operator++() noexcept + + { + for (std::size_t i = rank; i-- > 0;) + { + if (curr_[i] < boundary_[i] - 1) + { + curr_[i]++; + return *this; + } + curr_[i] = 0; + } + // If we're here we've wrapped over - set to past-the-end. + curr_ = boundary_; + return *this; + } + + constexpr bounds_iterator operator++(int)noexcept + { + auto ret = *this; + ++(*this); + return ret; + } + + constexpr bounds_iterator& operator--() + { + if (!less(curr_, boundary_)) + { + // if at the past-the-end, set to last element + for (std::size_t i = 0; i < rank; ++i) + { + curr_[i] = boundary_[i] - 1; + } + return *this; + } + for (std::size_t i = rank; i-- > 0;) + { + if (curr_[i] >= 1) + { + curr_[i]--; + return *this; + } + curr_[i] = boundary_[i] - 1; + } + // If we're here the preconditions were violated + // "pre: there exists s such that r == ++s" + detail::EXPECTS(false); + return *this; + } + + constexpr bounds_iterator operator--(int)noexcept + { + auto ret = *this; + --(*this); + return ret; + } + + constexpr bounds_iterator operator+(difference_type n) const noexcept + { + bounds_iterator ret{*this}; + return ret += n; + } + + constexpr bounds_iterator& operator+=(difference_type n) + { + auto linear_idx = linearize(curr_) + n; + std::remove_const_t stride = 0; + stride[rank - 1] = 1; + for (std::size_t i = rank - 1; i-- > 0;) + { + stride[i] = stride[i + 1] * boundary_[i + 1]; + } + for (std::size_t i = 0; i < rank; ++i) + { + curr_[i] = linear_idx / stride[i]; + linear_idx = linear_idx % stride[i]; + } + // index is out of bounds of the array + detail::EXPECTS(!less(curr_, index_type{}) && !less(boundary_, curr_)); + return *this; + } + + constexpr bounds_iterator operator-(difference_type n) const noexcept + { + bounds_iterator ret{*this}; + return ret -= n; + } + + constexpr bounds_iterator& operator-=(difference_type n) noexcept + { + return *this += -n; + } + + constexpr difference_type operator-(const bounds_iterator& rhs) const noexcept + { + return linearize(curr_) - linearize(rhs.curr_); + } + + constexpr value_type operator[](difference_type n) const noexcept + { + return *(*this + n); + } + + constexpr bool operator==(const bounds_iterator& rhs) const noexcept + { + return curr_ == rhs.curr_; + } + + constexpr bool operator!=(const bounds_iterator& rhs) const noexcept + { + return !(*this == rhs); + } + + constexpr bool operator<(const bounds_iterator& rhs) const noexcept + { + return less(curr_, rhs.curr_); + } + + constexpr bool operator<=(const bounds_iterator& rhs) const noexcept + { + return !(rhs < *this); + } + + constexpr bool operator>(const bounds_iterator& rhs) const noexcept + { + return rhs < *this; + } + + constexpr bool operator>=(const bounds_iterator& rhs) const noexcept + { + return !(rhs > *this); + } + + void swap(bounds_iterator& rhs) noexcept + { + std::swap(boundary_, rhs.boundary_); + std::swap(curr_, rhs.curr_); + } + +private: + constexpr bool less(index_type& one, index_type& other) const noexcept + { + for (std::size_t i = 0; i < rank; ++i) + { + if (one[i] < other[i]) + return true; + } + return false; + } + + constexpr index_size_type linearize(const value_type& idx) const noexcept + { + // TODO: Smarter impl. + // Check if past-the-end + index_size_type multiplier = 1; + index_size_type res = 0; + if (!less(idx, boundary_)) + { + res = 1; + for (std::size_t i = rank; i-- > 0;) + { + res += (idx[i] - 1) * multiplier; + multiplier *= boundary_[i]; + } + } + else + { + for (std::size_t i = rank; i-- > 0;) + { + res += idx[i] * multiplier; + multiplier *= boundary_[i]; + } + } + return res; + } + + value_type boundary_; + std::remove_const_t curr_; +}; + +template +bounds_iterator +operator+(typename bounds_iterator::difference_type n, + const bounds_iterator& rhs) noexcept +{ + return rhs + n; +} + +namespace detail +{ +template +constexpr std::enable_if_t< + std::is_same::value, + typename Bounds::index_type> +make_stride(const Bounds& bnd) noexcept +{ + return bnd.strides(); +} + +// Make a stride vector from bounds, assuming contiguous memory. +template +constexpr std::enable_if_t< + std::is_same::value, + typename Bounds::index_type> +make_stride(const Bounds& bnd) noexcept +{ + auto extents = bnd.index_bounds(); + typename Bounds::size_type stride[Bounds::rank] = {}; + + stride[Bounds::rank - 1] = 1; + for (std::size_t i = 1; i < Bounds::rank; ++i) + { + stride[Bounds::rank - i - 1] = + stride[Bounds::rank - i] * extents[Bounds::rank - i]; + } + return {stride}; +} + +template +void verifyBoundsReshape(const BoundsSrc& src, const BoundsDest& dest) +{ + static_assert(is_bounds::value && is_bounds::value, + "The src type and dest type must be bounds"); + static_assert(std::is_same::value, + "The source type must be a contiguous bounds"); + static_assert(BoundsDest::static_size == dynamic_range || + BoundsSrc::static_size == dynamic_range || + BoundsDest::static_size == BoundsSrc::static_size, + "The source bounds must have same size as dest bounds"); + detail::EXPECTS(src.size() == dest.size()); +} + +} // namespace detail + +template +class contiguous_span_iterator; +template +class general_span_iterator; + +template +struct dim_t +{ + static const std::ptrdiff_t value = DimSize; +}; +template <> +struct dim_t +{ + static const std::ptrdiff_t value = dynamic_range; + const std::ptrdiff_t dvalue; + constexpr dim_t(std::ptrdiff_t size) noexcept : dvalue(size) + { + } +}; + +template = 0)>> +constexpr dim_t dim() noexcept +{ + return dim_t(); +} + +template > +constexpr dim_t dim(std::ptrdiff_t n) noexcept +{ + return dim_t<>(n); +} + +template +class multi_span; + +template +class strided_span; + +namespace detail +{ +template +struct SpanTypeTraits +{ + using value_type = T; + using size_type = std::size_t; +}; + +template +struct SpanTypeTraits< + Traits, + typename std::is_reference::type> +{ + using value_type = typename Traits::span_traits::value_type; + using size_type = typename Traits::span_traits::size_type; +}; + +template +struct SpanArrayTraits +{ + using type = multi_span; + using value_type = T; + using bounds_type = static_bounds; + using pointer = T*; + using reference = T&; +}; +template +struct SpanArrayTraits : SpanArrayTraits +{ +}; + +template +BoundsType newBoundsHelperImpl(std::ptrdiff_t totalSize, + std::true_type) // dynamic size +{ + detail::EXPECTS(totalSize >= 0 && totalSize <= PTRDIFF_MAX); + return BoundsType{totalSize}; +} +template +BoundsType newBoundsHelperImpl(std::ptrdiff_t totalSize, + std::false_type) // static size +{ + detail::EXPECTS(BoundsType::static_size <= totalSize); + return {}; +} +template +BoundsType newBoundsHelper(std::ptrdiff_t totalSize) +{ + static_assert(BoundsType::dynamic_rank <= 1, + "dynamic rank must less or equal to 1"); + return newBoundsHelperImpl( + totalSize, std::integral_constant()); +} + +struct Sep +{ +}; + +template +T static_as_multi_span_helper(Sep, Args... args) +{ + return T{narrow_cast(args)...}; +} +template +std::enable_if_t>::value && + !std::is_same::value, + T> +static_as_multi_span_helper(Arg, Args... args) +{ + return static_as_multi_span_helper(args...); +} +template +T static_as_multi_span_helper(dim_t val, Args... args) +{ + return static_as_multi_span_helper(args..., val.dvalue); +} + +template +struct static_as_multi_span_static_bounds_helper +{ + using type = static_bounds<(Dimensions::value)...>; +}; + +template +struct is_multi_span_oracle : std::false_type +{ +}; + +template +struct is_multi_span_oracle< + multi_span> : std::true_type +{ +}; + +template +struct is_multi_span_oracle> : std::true_type +{ +}; + +template +struct is_multi_span : is_multi_span_oracle> +{ +}; +} // namespace detail + +template +class multi_span +{ + // TODO do we still need this? + template + friend class multi_span; + +public: + using bounds_type = static_bounds; + static const std::size_t Rank = bounds_type::rank; + using size_type = typename bounds_type::size_type; + using index_type = typename bounds_type::index_type; + using value_type = ValueType; + using const_value_type = std::add_const_t; + using pointer = std::add_pointer_t; + using reference = std::add_lvalue_reference_t; + using iterator = contiguous_span_iterator; + using const_span = + multi_span; + using const_iterator = contiguous_span_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using sliced_type = + std::conditional_t>; + +private: + pointer data_; + bounds_type bounds_; + + friend iterator; + friend const_iterator; + +public: + // default constructor - same as constructing from nullptr_t + constexpr multi_span() noexcept : multi_span(nullptr, bounds_type{}) + { + static_assert( + bounds_type::dynamic_rank != 0 || + (bounds_type::dynamic_rank == 0 && bounds_type::static_size == 0), + "Default construction of multi_span only possible " + "for dynamic or fixed, zero-length spans."); + } + + // construct from nullptr - get an empty multi_span + constexpr multi_span(std::nullptr_t) noexcept + : multi_span(nullptr, bounds_type{}) + { + static_assert( + bounds_type::dynamic_rank != 0 || + (bounds_type::dynamic_rank == 0 && bounds_type::static_size == 0), + "nullptr_t construction of multi_span only possible " + "for dynamic or fixed, zero-length spans."); + } + + // construct from nullptr with size of 0 (helps with template function calls) + template ::value>> + + constexpr multi_span(std::nullptr_t, IntType size) + : multi_span(nullptr, bounds_type{}) + { + static_assert( + bounds_type::dynamic_rank != 0 || + (bounds_type::dynamic_rank == 0 && bounds_type::static_size == 0), + "nullptr_t construction of multi_span only possible " + "for dynamic or fixed, zero-length spans."); + detail::EXPECTS(size == 0); + } + + // construct from a single element + + constexpr multi_span(reference data) noexcept + : multi_span(&data, bounds_type{1}) + { + static_assert(bounds_type::dynamic_rank > 0 || + bounds_type::static_size == 0 || + bounds_type::static_size == 1, + "Construction from a single element only possible " + "for dynamic or fixed spans of length 0 or 1."); + } + + // prevent constructing from temporaries for single-elements + constexpr multi_span(value_type&&) = delete; + + // construct from pointer + length + constexpr multi_span(pointer ptr, size_type size) + : multi_span(ptr, bounds_type{size}) + { + } + + // construct from pointer + length - multidimensional + constexpr multi_span(pointer data, bounds_type bounds) + : data_(data), bounds_(std::move(bounds)) + { + detail::EXPECTS((bounds_.size() > 0 && data != nullptr) || + bounds_.size() == 0); + } + + // construct from begin,end pointer pair + template ::value && + detail::LessThan::value>> + constexpr multi_span(pointer begin, Ptr end) + : multi_span(begin, + detail::newBoundsHelper( + static_cast(end) - begin)) + { + detail::EXPECTS(begin != nullptr && end != nullptr && + begin <= static_cast(end)); + } + + // construct from n-dimensions static array + template > + constexpr multi_span(T (&arr)[N]) + : multi_span(reinterpret_cast(arr), + bounds_type{typename Helper::bounds_type{}}) + { + static_assert(std::is_convertible::value, + "Cannot convert from source type to target multi_span type."); + static_assert( + std::is_convertible::value, + "Cannot construct a multi_span from an array with fewer elements."); + } + + // construct from n-dimensions dynamic array (e.g. new int[m][4]) + // (precedence will be lower than the 1-dimension pointer) + template > + constexpr multi_span(T* const& data, size_type size) + : multi_span(reinterpret_cast(data), + typename Helper::bounds_type{size}) + { + static_assert(std::is_convertible::value, + "Cannot convert from source type to target multi_span type."); + } + + // construct from std::array + template + constexpr multi_span(std::array& arr) + : multi_span(arr.data(), bounds_type{static_bounds{}}) + { + static_assert( + std::is_convertible(*)[]>::value, + "Cannot convert from source type to target multi_span type."); + static_assert( + std::is_convertible, bounds_type>::value, + "You cannot construct a multi_span from a std::array of smaller size."); + } + + // construct from const std::array + template + constexpr multi_span(const std::array& arr) + : multi_span(arr.data(), bounds_type{static_bounds{}}) + { + static_assert( + std::is_convertible(*)[]>::value, + "Cannot convert from source type to target multi_span type."); + static_assert( + std::is_convertible, bounds_type>::value, + "You cannot construct a multi_span from a std::array of smaller size."); + } + + // prevent constructing from temporary std::array + template + constexpr multi_span(std::array&& arr) = delete; + + // construct from containers + // future: could use contiguous_iterator_traits to identify only contiguous + // containers type-requirements: container must have .size(), operator[] which + // are value_type compatible + template ::value && + std::is_convertible::value && + std::is_same().size(), + *std::declval().data())>, + DataType>::value>> + constexpr multi_span(Cont& cont) + : multi_span(static_cast(cont.data()), + detail::newBoundsHelper( + narrow_cast(cont.size()))) + { + } + + // prevent constructing from temporary containers + template ::value && + std::is_convertible::value && + std::is_same().size(), + *std::declval().data())>, + DataType>::value>> + explicit constexpr multi_span(Cont&& cont) = delete; + + // construct from a convertible multi_span + template , + typename = std::enable_if_t< + std::is_convertible::value && + std::is_convertible::value>> + constexpr multi_span(multi_span other) + : data_(other.data_), bounds_(other.bounds_) + { + } + + // trivial copy and move + constexpr multi_span(const multi_span&) = default; + constexpr multi_span(multi_span&&) = default; + + // trivial assignment + constexpr multi_span& operator=(const multi_span&) = default; + constexpr multi_span& operator=(multi_span&&) = default; + + // first() - extract the first Count elements into a new multi_span + template + + constexpr multi_span first() const + { + static_assert(Count >= 0, "Count must be >= 0."); + static_assert(bounds_type::static_size == dynamic_range || + Count <= bounds_type::static_size, + "Count is out of bounds."); + + detail::EXPECTS(bounds_type::static_size != dynamic_range || + Count <= this->size()); + return {this->data(), Count}; + } + + // first() - extract the first count elements into a new multi_span + constexpr multi_span first(size_type count) const + { + detail::EXPECTS(count >= 0 && count <= this->size()); + return {this->data(), count}; + } + + // last() - extract the last Count elements into a new multi_span + template + constexpr multi_span last() const + { + static_assert(Count >= 0, "Count must be >= 0."); + static_assert(bounds_type::static_size == dynamic_range || + Count <= bounds_type::static_size, + "Count is out of bounds."); + + detail::EXPECTS(bounds_type::static_size != dynamic_range || + Count <= this->size()); + return {this->data() + this->size() - Count, Count}; + } + + // last() - extract the last count elements into a new multi_span + constexpr multi_span last(size_type count) const + { + detail::EXPECTS(count >= 0 && count <= this->size()); + return {this->data() + this->size() - count, count}; + } + + // subspan() - create a subview of Count elements starting at Offset + template + constexpr multi_span subspan() const + { + static_assert(Count >= 0, "Count must be >= 0."); + static_assert(Offset >= 0, "Offset must be >= 0."); + static_assert( + bounds_type::static_size == dynamic_range || + ((Offset <= bounds_type::static_size) && + Count <= bounds_type::static_size - Offset), + "You must describe a sub-range within bounds of the multi_span."); + + detail::EXPECTS(bounds_type::static_size != dynamic_range || + (Offset <= this->size() && Count <= this->size() - Offset)); + return {this->data() + Offset, Count}; + } + + // subspan() - create a subview of count elements starting at offset + // supplying dynamic_range for count will consume all available elements from + // offset + constexpr multi_span + subspan(size_type offset, size_type count = dynamic_range) const + { + detail::EXPECTS( + (offset >= 0 && offset <= this->size()) && + (count == dynamic_range || (count <= this->size() - offset))); + return {this->data() + offset, + count == dynamic_range ? this->length() - offset : count}; + } + + // section - creates a non-contiguous, strided multi_span from a contiguous + // one + constexpr strided_span section(index_type origin, + index_type extents) const + { + const size_type size = + this->bounds().total_size() - this->bounds().linearize(origin); + return {&this->operator[](origin), + size, + strided_bounds{extents, detail::make_stride(bounds())}}; + } + + // length of the multi_span in elements + constexpr size_type size() const noexcept + { + return bounds_.size(); + } + + // length of the multi_span in elements + constexpr size_type length() const noexcept + { + return this->size(); + } + + // length of the multi_span in bytes + constexpr size_type size_bytes() const noexcept + { + return narrow_cast(sizeof(value_type)) * this->size(); + } + + // length of the multi_span in bytes + constexpr size_type length_bytes() const noexcept + { + return this->size_bytes(); + } + + constexpr bool empty() const noexcept + { + return this->size() == 0; + } + + static constexpr std::size_t rank() + { + return Rank; + } + + template + constexpr size_type extent() const noexcept + { + static_assert( + Dim < Rank, + "Dimension should be less than rank (dimension count starts from 0)."); + return bounds_.template extent(); + } + + template + constexpr size_type extent(IntType dim) const + { + return bounds_.extent(dim); + } + + constexpr bounds_type bounds() const noexcept + { + return bounds_; + } + + constexpr pointer data() const noexcept + { + return data_; + } + + template + constexpr reference operator()(FirstIndex idx) + { + return this->operator[](narrow_cast(idx)); + } + + template + constexpr reference operator()(FirstIndex firstIndex, OtherIndices... indices) + { + const index_type idx = {narrow_cast(firstIndex), + narrow_cast(indices)...}; + return this->operator[](idx); + } + + constexpr reference operator[](const index_type& idx) const + { + return data_[bounds_.linearize(idx)]; + } + + template 1), + typename Ret = std::enable_if_t> + + constexpr Ret operator[](size_type idx) const + { + detail::EXPECTS( + idx >= 0 && idx < bounds_.size()); // index is out of bounds of the array + const size_type ridx = idx * bounds_.stride(); + + // index is out of bounds of the underlying data + detail::EXPECTS(ridx < bounds_.total_size()); + return Ret{data_ + ridx, bounds_.slice()}; + } + + constexpr iterator begin() const noexcept + { + return iterator{this, true}; + } + + constexpr iterator end() const noexcept + { + return iterator{this, false}; + } + + constexpr const_iterator cbegin() const noexcept + { + return const_iterator{reinterpret_cast(this), true}; + } + + constexpr const_iterator cend() const noexcept + { + return const_iterator{reinterpret_cast(this), false}; + } + + constexpr reverse_iterator rbegin() const noexcept + { + return reverse_iterator{end()}; + } + + constexpr reverse_iterator rend() const noexcept + { + return reverse_iterator{begin()}; + } + + constexpr const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator{cend()}; + } + + constexpr const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator{cbegin()}; + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator==(const multi_span& other) const + { + return bounds_.size() == other.bounds_.size() && + (data_ == other.data_ || + std::equal(this->begin(), this->end(), other.begin())); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator!=(const multi_span& other) const + { + return !(*this == other); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator<(const multi_span& other) const + { + return std::lexicographical_compare( + this->begin(), this->end(), other.begin(), other.end()); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator<=(const multi_span& other) const + { + return !(other < *this); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator>(const multi_span& other) const + noexcept + { + return (other < *this); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator>=(const multi_span& other) const + { + return !(*this < other); + } +}; + +// +// Free functions for manipulating spans +// + +// reshape a multi_span into a different dimensionality +// DimCount and Enabled here are workarounds for a bug in MSVC 2015 +template 0), + typename = std::enable_if_t> +constexpr auto as_multi_span(SpanType s, Dimensions2... dims) + -> multi_span +{ + static_assert(detail::is_multi_span::value, + "Variadic as_multi_span() is for reshaping existing spans."); + using BoundsType = typename multi_span::bounds_type; + const auto tobounds = + detail::static_as_multi_span_helper(dims..., detail::Sep{}); + detail::verifyBoundsReshape(s.bounds(), tobounds); + return {s.data(), tobounds}; +} + +template +constexpr auto as_multi_span(T* const& ptr, dim_t... args) + -> multi_span, Dimensions...> +{ + return {reinterpret_cast*>(ptr), + detail::static_as_multi_span_helper>( + args..., detail::Sep{})}; +} + +template +constexpr auto as_multi_span(T* arr, std::ptrdiff_t len) -> + typename detail::SpanArrayTraits::type +{ + return {reinterpret_cast*>(arr), len}; +} + +template +constexpr auto as_multi_span(T (&arr)[N]) -> + typename detail::SpanArrayTraits::type +{ + return {arr}; +} + +template +constexpr multi_span as_multi_span(const std::array& arr) +{ + return {arr}; +} + +template +constexpr multi_span +as_multi_span(const std::array&&) = delete; + +template +constexpr multi_span as_multi_span(std::array& arr) +{ + return {arr}; +} + +template +constexpr multi_span as_multi_span(T* begin, T* end) +{ + return {begin, end}; +} + +template +constexpr auto as_multi_span(Cont& arr) -> std::enable_if_t< + !detail::is_multi_span>::value, + multi_span, + dynamic_range>> +{ + detail::EXPECTS(arr.size() < PTRDIFF_MAX); + return {arr.data(), narrow_cast(arr.size())}; +} + +template +constexpr auto as_multi_span(Cont&& arr) -> std::enable_if_t< + !detail::is_multi_span>::value, + multi_span, + dynamic_range>> = delete; + +// from basic_string which doesn't have nonconst .data() member like other +// contiguous containers +template +constexpr auto as_multi_span(std::basic_string& str) + -> multi_span +{ + detail::EXPECTS(str.size() < PTRDIFF_MAX); + return {&str[0], narrow_cast(str.size())}; +} + +// It is kept here while the multidimensional interface is still being defined. +template +class strided_span +{ +public: + using bounds_type = strided_bounds; + using size_type = typename bounds_type::size_type; + using index_type = typename bounds_type::index_type; + using value_type = ValueType; + using const_value_type = std::add_const_t; + using pointer = std::add_pointer_t; + using reference = std::add_lvalue_reference_t; + using iterator = general_span_iterator; + using const_strided_span = strided_span; + using const_iterator = general_span_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using sliced_type = std:: + conditional_t>; + +private: + pointer data_; + bounds_type bounds_; + + friend iterator; + friend const_iterator; + template + friend class strided_span; + +public: + // from raw data + constexpr strided_span(pointer ptr, size_type size, bounds_type bounds) + : data_(ptr), bounds_(std::move(bounds)) + { + detail::EXPECTS((bounds_.size() > 0 && ptr != nullptr) || + bounds_.size() == 0); + // Bounds cross data boundaries + detail::EXPECTS(this->bounds().total_size() <= size); + (void)size; + } + + // from static array of size N + template + constexpr strided_span(value_type (&values)[N], bounds_type bounds) + : strided_span(values, N, std::move(bounds)) + { + } + + // from array view + template < + typename OtherValueType, + std::ptrdiff_t... Dimensions, + bool Enabled1 = (sizeof...(Dimensions) == Rank), + bool Enabled2 = std::is_convertible::value, + typename = std::enable_if_t> + constexpr strided_span(multi_span av, + bounds_type bounds) + : strided_span(av.data(), av.bounds().total_size(), std::move(bounds)) + { + } + + // convertible + template < + typename OtherValueType, + typename = std::enable_if_t< + std::is_convertible::value>> + constexpr strided_span(const strided_span& other) + : data_(other.data_), bounds_(other.bounds_) + { + } + + constexpr strided_span section(index_type origin, index_type extents) const + { + const size_type size = + this->bounds().total_size() - this->bounds().linearize(origin); + return {&this->operator[](origin), + size, + bounds_type{extents, detail::make_stride(bounds())}}; + } + + constexpr reference operator[](const index_type& idx) const + { + return data_[bounds_.linearize(idx)]; + } + + template 1), + typename Ret = std::enable_if_t> + constexpr Ret operator[](size_type idx) const + { + detail::EXPECTS(idx < + bounds_.size()); // index is out of bounds of the array + const size_type ridx = idx * bounds_.stride(); + + // index is out of bounds of the underlying data + detail::EXPECTS(ridx < bounds_.total_size()); + return {data_ + ridx, bounds_.slice().total_size(), bounds_.slice()}; + } + + constexpr bounds_type bounds() const noexcept + { + return bounds_; + } + + template + constexpr size_type extent() const noexcept + { + static_assert( + Dim < Rank, + "dimension should be less than Rank (dimension count starts from 0)"); + return bounds_.template extent(); + } + + constexpr size_type size() const noexcept + { + return bounds_.size(); + } + + constexpr pointer data() const noexcept + { + return data_; + } + + constexpr bool empty() const noexcept + { + return this->size() == 0; + } + + constexpr explicit operator bool() const noexcept + { + return data_ != nullptr; + } + + constexpr iterator begin() const + { + return iterator{this, true}; + } + + constexpr iterator end() const + { + return iterator{this, false}; + } + + constexpr const_iterator cbegin() const + { + return const_iterator{reinterpret_cast(this), + true}; + } + + constexpr const_iterator cend() const + { + return const_iterator{reinterpret_cast(this), + false}; + } + + constexpr reverse_iterator rbegin() const + { + return reverse_iterator{end()}; + } + + constexpr reverse_iterator rend() const + { + return reverse_iterator{begin()}; + } + + constexpr const_reverse_iterator crbegin() const + { + return const_reverse_iterator{cend()}; + } + + constexpr const_reverse_iterator crend() const + { + return const_reverse_iterator{cbegin()}; + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator==(const strided_span& other) const + { + return bounds_.size() == other.bounds_.size() && + (data_ == other.data_ || + std::equal(this->begin(), this->end(), other.begin())); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator!=(const strided_span& other) const + { + return !(*this == other); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator<(const strided_span& other) const + { + return std::lexicographical_compare( + this->begin(), this->end(), other.begin(), other.end()); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator<=(const strided_span& other) const + { + return !(other < *this); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator>(const strided_span& other) const + { + return (other < *this); + } + + template , + std::remove_cv_t>::value>> + constexpr bool + operator>=(const strided_span& other) const + { + return !(*this < other); + } + +private: + static index_type resize_extent(const index_type& extent, std::ptrdiff_t d) + { + // The last dimension of the array needs to contain a multiple of new type + // elements + detail::EXPECTS(extent[Rank - 1] >= d && (extent[Rank - 1] % d == 0)); + + index_type ret = extent; + ret[Rank - 1] /= d; + + return ret; + } + + template > + static index_type + resize_stride(const index_type& strides, std::ptrdiff_t, void* = nullptr) + { + // Only strided arrays with regular strides can be resized + detail::EXPECTS(strides[Rank - 1] == 1); + + return strides; + } + + template 1), typename = std::enable_if_t> + static index_type resize_stride(const index_type& strides, std::ptrdiff_t d) + { + // Only strided arrays with regular strides can be resized + detail::EXPECTS(strides[Rank - 1] == 1); + // The strides must have contiguous chunks of + // memory that can contain a multiple of new type elements + detail::EXPECTS(strides[Rank - 2] >= d && (strides[Rank - 2] % d == 0)); + + for (std::size_t i = Rank - 1; i > 0; --i) + { + // Only strided arrays with regular strides can be resized + detail::EXPECTS((strides[i - 1] >= strides[i]) && + (strides[i - 1] % strides[i] == 0)); + } + + index_type ret = strides / d; + ret[Rank - 1] = 1; + + return ret; + } +}; + +template +class contiguous_span_iterator +{ +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = typename Span::value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + +private: + template + friend class multi_span; + + pointer data_; + const Span* m_validator; + + void validateThis() const + { + // iterator is out of range of the array + detail::EXPECTS(data_ >= m_validator->data_ && + data_ < m_validator->data_ + m_validator->size()); + } + + contiguous_span_iterator(const Span* container, bool isbegin) + : data_(isbegin ? container->data_ : container->data_ + container->size()) + , m_validator(container) + { + } + +public: + reference operator*() const + { + validateThis(); + return *data_; + } + pointer operator->() const + { + validateThis(); + return data_; + } + + contiguous_span_iterator& operator++() noexcept + { + ++data_; + return *this; + } + contiguous_span_iterator operator++(int)noexcept + { + auto ret = *this; + ++(*this); + return ret; + } + + contiguous_span_iterator& operator--() noexcept + { + --data_; + return *this; + } + contiguous_span_iterator operator--(int)noexcept + { + auto ret = *this; + --(*this); + return ret; + } + contiguous_span_iterator operator+(difference_type n) const noexcept + { + contiguous_span_iterator ret{*this}; + return ret += n; + } + contiguous_span_iterator& operator+=(difference_type n) noexcept + { + data_ += n; + return *this; + } + contiguous_span_iterator operator-(difference_type n) const noexcept + { + contiguous_span_iterator ret{*this}; + return ret -= n; + } + + contiguous_span_iterator& operator-=(difference_type n) + { + return *this += -n; + } + difference_type operator-(const contiguous_span_iterator& rhs) const + { + detail::EXPECTS(m_validator == rhs.m_validator); + return data_ - rhs.data_; + } + reference operator[](difference_type n) const + { + return *(*this + n); + } + bool operator==(const contiguous_span_iterator& rhs) const + { + detail::EXPECTS(m_validator == rhs.m_validator); + return data_ == rhs.data_; + } + + bool operator!=(const contiguous_span_iterator& rhs) const + { + return !(*this == rhs); + } + + bool operator<(const contiguous_span_iterator& rhs) const + { + detail::EXPECTS(m_validator == rhs.m_validator); + return data_ < rhs.data_; + } + + bool operator<=(const contiguous_span_iterator& rhs) const + { + return !(rhs < *this); + } + bool operator>(const contiguous_span_iterator& rhs) const + { + return rhs < *this; + } + bool operator>=(const contiguous_span_iterator& rhs) const + { + return !(rhs > *this); + } + + void swap(contiguous_span_iterator& rhs) noexcept + { + std::swap(data_, rhs.data_); + std::swap(m_validator, rhs.m_validator); + } +}; + +template +contiguous_span_iterator +operator+(typename contiguous_span_iterator::difference_type n, + const contiguous_span_iterator& rhs) noexcept +{ + return rhs + n; +} + +template +class general_span_iterator +{ +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = typename Span::value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + +private: + template + friend class strided_span; + + const Span* m_container; + typename Span::bounds_type::iterator m_itr; + general_span_iterator(const Span* container, bool isbegin) + : m_container(container) + , m_itr(isbegin ? m_container->bounds().begin() + : m_container->bounds().end()) + { + } + +public: + reference operator*() noexcept + { + return (*m_container)[*m_itr]; + } + pointer operator->() noexcept + { + return &(*m_container)[*m_itr]; + } + general_span_iterator& operator++() noexcept + { + ++m_itr; + return *this; + } + general_span_iterator operator++(int)noexcept + { + auto ret = *this; + ++(*this); + return ret; + } + general_span_iterator& operator--() noexcept + { + --m_itr; + return *this; + } + general_span_iterator operator--(int)noexcept + { + auto ret = *this; + --(*this); + return ret; + } + general_span_iterator operator+(difference_type n) const noexcept + { + general_span_iterator ret{*this}; + return ret += n; + } + general_span_iterator& operator+=(difference_type n) noexcept + { + m_itr += n; + return *this; + } + general_span_iterator operator-(difference_type n) const noexcept + { + general_span_iterator ret{*this}; + return ret -= n; + } + general_span_iterator& operator-=(difference_type n) noexcept + { + return *this += -n; + } + difference_type operator-(const general_span_iterator& rhs) const + { + detail::EXPECTS(m_container == rhs.m_container); + return m_itr - rhs.m_itr; + } + + value_type operator[](difference_type n) const + { + return (*m_container)[m_itr[n]]; + } + + bool operator==(const general_span_iterator& rhs) const + { + detail::EXPECTS(m_container == rhs.m_container); + return m_itr == rhs.m_itr; + } + bool operator!=(const general_span_iterator& rhs) const + { + return !(*this == rhs); + } + bool operator<(const general_span_iterator& rhs) const + { + detail::EXPECTS(m_container == rhs.m_container); + return m_itr < rhs.m_itr; + } + bool operator<=(const general_span_iterator& rhs) const + { + return !(rhs < *this); + } + bool operator>(const general_span_iterator& rhs) const + { + return rhs < *this; + } + bool operator>=(const general_span_iterator& rhs) const + { + return !(rhs > *this); + } + void swap(general_span_iterator& rhs) noexcept + { + std::swap(m_itr, rhs.m_itr); + std::swap(m_container, rhs.m_container); + } +}; + +template +general_span_iterator +operator+(typename general_span_iterator::difference_type n, + const general_span_iterator& rhs) noexcept +{ + return rhs + n; +} + +} // namespace stdex + +#if defined(_MSC_VER) && !defined(__clang__) +#if _MSC_VER < 1910 + +#undef constexpr +#pragma pop_macro("constexpr") +#endif // _MSC_VER < 1910 + +#pragma warning(pop) + +#endif // _MSC_VER + +#if defined(__GNUC__) && __GNUC__ > 6 +#pragma GCC diagnostic pop +#endif // __GNUC__ > 6 + +#endif // MULTI_SPAN_H diff --git a/test/BUILD b/test/BUILD new file mode 100644 index 0000000..fb91ef7 --- /dev/null +++ b/test/BUILD @@ -0,0 +1,63 @@ + +BASE_OPTS = ["-Wall", "-Wextra", "-pedantic", "-Wno-array-bounds",] + +MULTI_SPAN_TESTS_COPTS = select({ + "@bazel_tools//src/conditions:windows": [].extend(BASE_OPTS), + "//conditions:default": [].extend(BASE_OPTS), +}) + +MULTI_SPAN_TESTS_DEFINES = ["MULTI_SPAN_THROW_ON_CONTRACT_VIOLATION"] + +cc_library( + name = "test_main", + srcs = ["src/tests.cpp"], + deps = [ + "@catch2//:catch2", + ], + alwayslink = True, +) + +cc_test( + name = "bounds_tests", + size = "small", + srcs = [ + "src/bounds_tests.cpp", + ], + copts = MULTI_SPAN_TESTS_COPTS, + defines = MULTI_SPAN_TESTS_DEFINES, + deps = [ + ":test_main", + "//:multi_span", + "@catch2//:catch2", + ], +) + +cc_test( + name = "multi_span_tests", + size = "small", + srcs = [ + "src/multi_span_tests.cpp", + ], + copts = MULTI_SPAN_TESTS_COPTS, + defines = MULTI_SPAN_TESTS_DEFINES, + deps = [ + ":test_main", + "//:multi_span", + "@catch2//:catch2", + ], +) + +cc_test( + name = "strided_span_tests", + size = "small", + srcs = [ + "src/strided_span_tests.cpp", + ], + copts = MULTI_SPAN_TESTS_COPTS, + defines = MULTI_SPAN_TESTS_DEFINES, + deps = [ + ":test_main", + "//:multi_span", + "@catch2//:catch2", + ], +) diff --git a/test/src/bounds_tests.cpp b/test/src/bounds_tests.cpp new file mode 100644 index 0000000..89a170e --- /dev/null +++ b/test/src/bounds_tests.cpp @@ -0,0 +1,115 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Maintained by Jack Diver. +// +// Modified from :- +// +// github.com/microsoft/GSL +// +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +// blanket turn off warnings from CppCoreCheck from catch +// so people aren't annoyed by them when running the tool. +#pragma warning(disable : 26440 26426) // from catch +#endif + +#include +// for multi_span, contiguous_span_iterator, dim +#include + +#include // for ptrdiff_t, size_t + +using namespace std; +using namespace stdex; + +namespace +{ +void use(std::ptrdiff_t&) +{ +} +} + +TEST_CASE("basic_bounds") +{ + for (auto point : static_bounds{2}) + { + for (decltype(point)::size_type j = 0; + j < static_cast(decltype(point)::rank); + j++) + { + use(j); + use(point[static_cast(j)]); + } + } +} + +TEST_CASE("bounds_basic") +{ + static_bounds<3, 4, 5> b; + const auto a = b.slice(); + (void)a; + static_bounds<4, dynamic_range, 2> x{4}; + x.slice().slice(); +} + +TEST_CASE("arrayview_iterator") +{ + static_bounds<4, dynamic_range, 2> bounds{3}; + + const auto itr = bounds.begin(); + (void)itr; +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span av(nullptr, bounds); + + auto itr2 = av.cbegin(); + + for (auto& v : av) + { + v = 4; + } + fill(av.begin(), av.end(), 0); +#endif +} + +TEST_CASE("bounds_convertible") +{ + static_bounds<7, 4, 2> b1; + static_bounds<7, dynamic_range, 2> b2 = b1; + (void)b2; +#ifdef CONFIRM_COMPILATION_ERRORS + static_bounds<7, dynamic_range, 1> b4 = b2; +#endif + + static_bounds b3 = b1; + static_bounds<7, 4, 2> b4 = b3; + (void)b4; + + static_bounds b11; + + static_bounds b5; + static_bounds<34> b6; + + b5 = static_bounds<20>(); + CHECK_THROWS_AS(b6 = b5, std::logic_error); + b5 = static_bounds<34>(); + b6 = b5; + + CHECK(b5 == b6); + CHECK(b5.size() == b6.size()); +} + +#ifdef CONFIRM_COMPILATION_ERRORS +copy(src_span_static, dst_span_static); +#endif diff --git a/test/src/multi_span_tests.cpp b/test/src/multi_span_tests.cpp new file mode 100644 index 0000000..f183c7e --- /dev/null +++ b/test/src/multi_span_tests.cpp @@ -0,0 +1,1720 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Maintained by Jack Diver. +// +// Modified from :- +// +// github.com/microsoft/GSL +// +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +// blanket turn off warnings from CppCoreCheck from catch +// so people aren't annoyed by them when running the tool. +#pragma warning(disable : 26440 26426) +// Suppressing warnings until it is completely removed +#endif + +#include +// for multi_span, contiguous_span_iterator, dim +#include + +#include // for fill, for_each +#include // for array +#include // for ptrdiff_t, size_t +#include // for reverse_iterator, begin, end, operator!= +#include // for iota +#include // for ptrdiff_t +#include // for string +#include // for vector + +using namespace std; +using namespace stdex; + +namespace +{ +struct BaseClass +{ +}; +struct DerivedClass : BaseClass +{ +}; +} // namespace + +TEST_CASE("default_constructor") +{ + { + multi_span s; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { + multi_span s; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s; + CHECK((s.length() == 1 && + s.data() == nullptr)); // explains why it can't compile +#endif + } + + { + multi_span s{}; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs{}; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } +} + +TEST_CASE("from_nullptr_constructor") +{ + { + multi_span s = nullptr; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs = nullptr; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { + multi_span s = nullptr; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs = nullptr; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s = nullptr; + CHECK((s.length() == 1 && + s.data() == nullptr)); // explains why it can't compile +#endif + } + + { + multi_span s{nullptr}; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs{nullptr}; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { + multi_span s{nullptr}; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs{nullptr}; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } +} + +TEST_CASE("from_nullptr_length_constructor") +{ + { + multi_span s{nullptr, 0}; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs{nullptr, 0}; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { + multi_span s{nullptr, 0}; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs{nullptr, 0}; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { + auto workaround_macro = []() { const multi_span s{nullptr, 1}; }; + CHECK_THROWS_AS(workaround_macro(), std::logic_error); + + auto const_workaround_macro = []() { + const multi_span cs{nullptr, 1}; + }; + CHECK_THROWS_AS(const_workaround_macro(), std::logic_error); + } + + { + auto workaround_macro = []() { const multi_span s{nullptr, 1}; }; + CHECK_THROWS_AS(workaround_macro(), std::logic_error); + + auto const_workaround_macro = []() { + const multi_span s{nullptr, 1}; + }; + CHECK_THROWS_AS(const_workaround_macro(), std::logic_error); + } + + { + multi_span s{nullptr, 0}; + CHECK((s.length() == 0 && s.data() == nullptr)); + + multi_span cs{nullptr, 0}; + CHECK((cs.length() == 0 && cs.data() == nullptr)); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{nullptr, 0}; + CHECK((s.length() == 1 && + s.data() == nullptr)); // explains why it can't compile +#endif + } +} + +TEST_CASE("from_element_constructor") +{ + int i = 5; + + { + multi_span s = i; + CHECK((s.length() == 1 && s.data() == &i)); + CHECK(s[0] == 5); + + multi_span cs = i; + CHECK((cs.length() == 1 && cs.data() == &i)); + CHECK(cs[0] == 5); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + const j = 1; + multi_span s = j; +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s = i; + CHECK((s.length() == 0 && s.data() == &i)); +#endif + } + + { + multi_span s = i; + CHECK((s.length() == 1 && s.data() == &i)); + CHECK(s[0] == 5); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s = i; + CHECK((s.length() == 2 && s.data() == &i)); +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + auto get_a_temp = []() -> int { return 4; }; + auto use_a_span = [](multi_span s) { (void)s; }; + use_a_span(get_a_temp()); +#endif + } +} + +TEST_CASE("from_pointer_length_constructor") +{ + int arr[4] = {1, 2, 3, 4}; + + { + multi_span s{&arr[0], 2}; + CHECK((s.length() == 2 && s.data() == &arr[0])); + CHECK((s[0] == 1 && s[1] == 2)); + } + + { + multi_span s{&arr[0], 2}; + CHECK((s.length() == 2 && s.data() == &arr[0])); + CHECK((s[0] == 1 && s[1] == 2)); + } + + { + int* p = nullptr; + multi_span s{p, 0}; + CHECK((s.length() == 0 && s.data() == nullptr)); + } + + { + int* p = nullptr; + auto workaround_macro = [=]() { const multi_span s{p, 2}; }; + CHECK_THROWS_AS(workaround_macro(), std::logic_error); + } +} + +TEST_CASE("from_pointer_pointer_constructor") +{ + int arr[4] = {1, 2, 3, 4}; + + { + multi_span s{&arr[0], &arr[2]}; + CHECK((s.length() == 2 && s.data() == &arr[0])); + CHECK((s[0] == 1 && s[1] == 2)); + } + + { + multi_span s{&arr[0], &arr[2]}; + CHECK((s.length() == 2 && s.data() == &arr[0])); + CHECK((s[0] == 1 && s[1] == 2)); + } + + { + multi_span s{&arr[0], &arr[0]}; + CHECK((s.length() == 0 && s.data() == &arr[0])); + } + + { + multi_span s{&arr[0], &arr[0]}; + CHECK((s.length() == 0 && s.data() == &arr[0])); + } + + { + auto workaround_macro = [&]() { + const multi_span s{&arr[1], &arr[0]}; + }; + CHECK_THROWS_AS(workaround_macro(), std::logic_error); + } + + { + int* p = nullptr; + auto workaround_macro = [&]() { const multi_span s{&arr[0], p}; }; + CHECK_THROWS_AS(workaround_macro(), std::logic_error); + } + + { + int* p = nullptr; + auto workaround_macro = [&]() { const multi_span s{p, p}; }; + CHECK_THROWS_AS(workaround_macro(), std::logic_error); + } + + { + int* p = nullptr; + auto workaround_macro = [&]() { const multi_span s{&arr[0], p}; }; + CHECK_THROWS_AS(workaround_macro(), std::logic_error); + } +} + +TEST_CASE("from_array_constructor") +{ + int arr[5] = {1, 2, 3, 4, 5}; + + { + multi_span s{arr}; + CHECK((s.length() == 5 && s.data() == &arr[0])); + } + + { + multi_span s{arr}; + CHECK((s.length() == 5 && s.data() == &arr[0])); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr}; +#endif + } + + { + multi_span s{arr}; + CHECK((s.length() == 0 && s.data() == &arr[0])); + } + + int arr2d[2][3] = {1, 2, 3, 4, 5, 6}; + + { + multi_span s{arr2d}; + CHECK((s.length() == 6 && s.data() == &arr2d[0][0])); + CHECK((s[0] == 1 && s[5] == 6)); + } + + { + multi_span s{arr2d}; + CHECK((s.length() == 0 && s.data() == &arr2d[0][0])); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr2d}; +#endif + } + + { + multi_span s{arr2d}; + CHECK((s.length() == 6 && s.data() == &arr2d[0][0])); + CHECK((s[0] == 1 && s[5] == 6)); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr2d}; +#endif + } + + { + multi_span s{arr2d[0]}; + CHECK((s.length() == 1 && s.data() == &arr2d[0])); + } + + { + multi_span s{arr2d}; + CHECK((s.length() == 6 && s.data() == &arr2d[0][0])); + auto workaround_macro = [&]() { return s[{1, 2}] == 6; }; + CHECK(workaround_macro()); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr2d}; +#endif + } + + int arr3d[2][3][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + { + multi_span s{arr3d}; + CHECK((s.length() == 12 && s.data() == &arr3d[0][0][0])); + CHECK((s[0] == 1 && s[11] == 12)); + } + + { + multi_span s{arr3d}; + CHECK((s.length() == 0 && s.data() == &arr3d[0][0][0])); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr3d}; +#endif + } + + { + multi_span s{arr3d}; + CHECK((s.length() == 12 && s.data() == &arr3d[0][0][0])); + CHECK((s[0] == 1 && s[5] == 6)); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr3d}; +#endif + } + + { + multi_span s{arr3d[0]}; + CHECK((s.length() == 1 && s.data() == &arr3d[0])); + } + + { + multi_span s{arr3d}; + CHECK((s.length() == 12 && s.data() == &arr3d[0][0][0])); + auto workaround_macro = [&]() { return s[{2, 1, 0}] == 11; }; + CHECK(workaround_macro()); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr3d}; +#endif + } +} + +TEST_CASE("from_dynamic_array_constructor") +{ + double(*arr)[3][4] = new double[100][3][4]; + + { + multi_span s(arr, 10); + CHECK((s.length() == 120 && s.data() == &arr[0][0][0])); + CHECK_THROWS_AS(s[10][3][4], std::logic_error); + } + + { + multi_span s(arr, 10); + CHECK((s.length() == 120 && s.data() == &arr[0][0][0])); + } + + { + multi_span s(arr, 10); + CHECK((s.length() == 120 && s.data() == &arr[0][0][0])); + } + + { + multi_span s(arr, 0); + CHECK((s.length() == 0 && s.data() == &arr[0][0][0])); + } + + delete[] arr; +} + +TEST_CASE("from_std_array_constructor") +{ + std::array arr = {1, 2, 3, 4}; + + { + multi_span s{arr}; + CHECK((s.size() == narrow_cast(arr.size()) && + s.data() == arr.data())); + + multi_span cs{arr}; + CHECK((cs.size() == narrow_cast(arr.size()) && + cs.data() == arr.data())); + } + + { + multi_span s{arr}; + CHECK((s.size() == narrow_cast(arr.size()) && + s.data() == arr.data())); + + multi_span cs{arr}; + CHECK((cs.size() == narrow_cast(arr.size()) && + cs.data() == arr.data())); + } + + { + multi_span s{arr}; + CHECK((s.size() == 2 && s.data() == arr.data())); + + multi_span cs{arr}; + CHECK((cs.size() == 2 && cs.data() == arr.data())); + } + + { + multi_span s{arr}; + CHECK((s.size() == 0 && s.data() == arr.data())); + + multi_span cs{arr}; + CHECK((cs.size() == 0 && cs.data() == arr.data())); + } + + // TODO This is currently an unsupported scenario. We will come back to it as + // we revise the multidimensional interface and what transformations between + // dimensionality look like + //{ + // multi_span s{arr}; + // CHECK(s.size() == narrow_cast(arr.size()) && s.data() == + // arr.data()); + //} + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr}; +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + auto get_an_array = []() { return std::array{1, 2, 3, 4}; }; + auto take_a_span = [](multi_span s) { (void)s; }; + // try to take a temporary std::array + take_a_span(get_an_array()); +#endif + } +} + +TEST_CASE("from_const_std_array_constructor") +{ + const std::array arr = {1, 2, 3, 4}; + + { + multi_span s{arr}; + CHECK((s.size() == narrow_cast(arr.size()) && + s.data() == arr.data())); + } + + { + multi_span s{arr}; + CHECK((s.size() == narrow_cast(arr.size()) && + s.data() == arr.data())); + } + + { + multi_span s{arr}; + CHECK((s.size() == 2 && s.data() == arr.data())); + } + + { + multi_span s{arr}; + CHECK((s.size() == 0 && s.data() == arr.data())); + } + + // TODO This is currently an unsupported scenario. We will come back to it as + // we revise the multidimensional interface and what transformations between + // dimensionality look like + //{ + // multi_span s{arr}; + // CHECK(s.size() == narrow_cast(arr.size()) && s.data() == + // arr.data()); + //} + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{arr}; +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + auto get_an_array = []() -> const std::array { + return {1, 2, 3, 4}; + }; + auto take_a_span = [](multi_span s) { (void)s; }; + // try to take a temporary std::array + take_a_span(get_an_array()); +#endif + } +} + +TEST_CASE("from_container_constructor") +{ + std::vector v = {1, 2, 3}; + const std::vector cv = v; + + { + multi_span s{v}; + CHECK((s.size() == narrow_cast(v.size()) && + s.data() == v.data())); + + multi_span cs{v}; + CHECK((cs.size() == narrow_cast(v.size()) && + cs.data() == v.data())); + } + + std::string str = "hello"; + const std::string cstr = "hello"; + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{str}; + CHECK((s.size() == narrow_cast(str.size()) && + s.data() == str.data())); +#endif + multi_span cs{str}; + CHECK((cs.size() == narrow_cast(str.size()) && + cs.data() == str.data())); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span s{cstr}; +#endif + multi_span cs{cstr}; + CHECK((cs.size() == narrow_cast(cstr.size()) && + cs.data() == cstr.data())); + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + auto get_temp_vector = []() -> std::vector { return {}; }; + auto use_span = [](multi_span s) { (void)s; }; + use_span(get_temp_vector()); +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + auto get_temp_string = []() -> std::string { return {}; }; + auto use_span = [](multi_span s) { (void)s; }; + use_span(get_temp_string()); +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + auto get_temp_vector = []() -> const std::vector { return {}; }; + auto use_span = [](multi_span s) { (void)s; }; + use_span(get_temp_vector()); +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + auto get_temp_string = []() -> const std::string { return {}; }; + auto use_span = [](multi_span s) { (void)s; }; + use_span(get_temp_string()); +#endif + } + + { +#ifdef CONFIRM_COMPILATION_ERRORS + std::map m; + multi_span s{m}; +#endif + } +} + +TEST_CASE("from_convertible_span_constructor") +{ +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span av1(nullptr, b1); + + auto f = [&]() { multi_span av1(nullptr); }; + CHECK_THROWS_AS(f(), std::logic_error); +#endif + +#ifdef CONFIRM_COMPILATION_ERRORS + static_bounds b12(b11); + b12 = b11; + b11 = b12; + + multi_span av1 = nullptr; + multi_span av2(av1); + multi_span av2(av1); +#endif + + multi_span avd; +#ifdef CONFIRM_COMPILATION_ERRORS + multi_span avb = avd; +#endif + multi_span avcd = avd; + (void)avcd; +} + +TEST_CASE("copy_move_and_assignment") +{ + multi_span s1; + CHECK(s1.empty()); + + int arr[] = {3, 4, 5}; + + multi_span s2 = arr; + CHECK((s2.length() == 3 && s2.data() == &arr[0])); + + s2 = s1; + CHECK(s2.empty()); + + auto get_temp_span = [&]() -> multi_span { return {&arr[1], 2}; }; + auto use_span = [&](multi_span s) { + CHECK((s.length() == 2 && s.data() == &arr[1])); + }; + use_span(get_temp_span()); + + s1 = get_temp_span(); + CHECK((s1.length() == 2 && s1.data() == &arr[1])); +} + +template +void fn(const Bounds&) +{ + static_assert(Bounds::static_size == 60, "static bounds is wrong size"); +} + +TEST_CASE("as_multi_span_reshape") +{ + int a[3][4][5]; + auto av = as_multi_span(a); + fn(av.bounds()); + auto av2 = as_multi_span(av, dim<60>()); + auto av3 = as_multi_span(av2, dim<3>(), dim<4>(), dim<5>()); + auto av4 = as_multi_span(av3, dim<4>(), dim(3), dim<5>()); + auto av5 = as_multi_span(av4, dim<3>(), dim<4>(), dim<5>()); + auto av6 = as_multi_span(av5, dim<12>(), dim(5)); + (void)av6; +} + +TEST_CASE("first") +{ + int arr[5] = {1, 2, 3, 4, 5}; + + { + multi_span av = arr; + CHECK((av.first<2>().bounds() == static_bounds<2>())); + CHECK(av.first<2>().length() == 2); + CHECK(av.first(2).length() == 2); + } + + { + multi_span av = arr; + CHECK((av.first<0>().bounds() == static_bounds<0>())); + CHECK(av.first<0>().length() == 0); + CHECK(av.first(0).length() == 0); + } + + { + multi_span av = arr; + CHECK((av.first<5>().bounds() == static_bounds<5>())); + CHECK(av.first<5>().length() == 5); + CHECK(av.first(5).length() == 5); + } + + { + multi_span av = arr; +#ifdef CONFIRM_COMPILATION_ERRORS + CHECK(av.first<6>().bounds() == static_bounds<6>()); + CHECK(av.first<6>().length() == 6); + CHECK(av.first<-1>().length() == -1); +#endif + CHECK_THROWS_AS(av.first(6).length(), std::logic_error); + } + + { + multi_span av; + CHECK((av.first<0>().bounds() == static_bounds<0>())); + CHECK(av.first<0>().length() == 0); + CHECK(av.first(0).length() == 0); + } +} + +TEST_CASE("last") +{ + int arr[5] = {1, 2, 3, 4, 5}; + + { + multi_span av = arr; + CHECK((av.last<2>().bounds() == static_bounds<2>())); + CHECK(av.last<2>().length() == 2); + CHECK(av.last(2).length() == 2); + } + + { + multi_span av = arr; + CHECK((av.last<0>().bounds() == static_bounds<0>())); + CHECK(av.last<0>().length() == 0); + CHECK(av.last(0).length() == 0); + } + + { + multi_span av = arr; + CHECK((av.last<5>().bounds() == static_bounds<5>())); + CHECK(av.last<5>().length() == 5); + CHECK(av.last(5).length() == 5); + } + + { + multi_span av = arr; +#ifdef CONFIRM_COMPILATION_ERRORS + CHECK((av.last<6>().bounds() == static_bounds<6>())); + CHECK(av.last<6>().length() == 6); +#endif + CHECK_THROWS_AS(av.last(6).length(), std::logic_error); + } + + { + multi_span av; + CHECK((av.last<0>().bounds() == static_bounds<0>())); + CHECK(av.last<0>().length() == 0); + CHECK(av.last(0).length() == 0); + } +} + +TEST_CASE("subspan") +{ + int arr[5] = {1, 2, 3, 4, 5}; + + { + multi_span av = arr; + CHECK((av.subspan<2, 2>().bounds() == static_bounds<2>())); + CHECK((av.subspan<2, 2>().length() == 2)); + CHECK(av.subspan(2, 2).length() == 2); + CHECK(av.subspan(2, 3).length() == 3); + } + + { + multi_span av = arr; + CHECK((av.subspan<0, 0>().bounds() == static_bounds<0>())); + CHECK((av.subspan<0, 0>().length() == 0)); + CHECK(av.subspan(0, 0).length() == 0); + } + + { + multi_span av = arr; + CHECK((av.subspan<0, 5>().bounds() == static_bounds<5>())); + CHECK((av.subspan<0, 5>().length() == 5)); + CHECK(av.subspan(0, 5).length() == 5); + CHECK_THROWS_AS(av.subspan(0, 6).length(), std::logic_error); + CHECK_THROWS_AS(av.subspan(1, 5).length(), std::logic_error); + } + + { + multi_span av = arr; + CHECK((av.subspan<5, 0>().bounds() == static_bounds<0>())); + CHECK((av.subspan<5, 0>().length() == 0)); + CHECK(av.subspan(5, 0).length() == 0); + CHECK_THROWS_AS(av.subspan(6, 0).length(), std::logic_error); + } + + { + multi_span av; + CHECK((av.subspan<0, 0>().bounds() == static_bounds<0>())); + CHECK((av.subspan<0, 0>().length() == 0)); + CHECK(av.subspan(0, 0).length() == 0); + CHECK_THROWS_AS((av.subspan<1, 0>().length()), std::logic_error); + } + + { + multi_span av; + CHECK(av.subspan(0).length() == 0); + CHECK_THROWS_AS(av.subspan(1).length(), std::logic_error); + } + + { + multi_span av = arr; + CHECK(av.subspan(0).length() == 5); + CHECK(av.subspan(1).length() == 4); + CHECK(av.subspan(4).length() == 1); + CHECK(av.subspan(5).length() == 0); + CHECK_THROWS_AS(av.subspan(6).length(), std::logic_error); + auto av2 = av.subspan(1); + for (int i = 0; i < 4; ++i) + CHECK(av2[i] == i + 2); + } + + { + multi_span av = arr; + CHECK(av.subspan(0).length() == 5); + CHECK(av.subspan(1).length() == 4); + CHECK(av.subspan(4).length() == 1); + CHECK(av.subspan(5).length() == 0); + CHECK_THROWS_AS(av.subspan(6).length(), std::logic_error); + auto av2 = av.subspan(1); + for (int i = 0; i < 4; ++i) + CHECK(av2[i] == i + 2); + } +} + +TEST_CASE("rank") +{ + int arr[2] = {1, 2}; + + { + multi_span s; + CHECK(s.rank() == 1); + } + + { + multi_span s = arr; + CHECK(s.rank() == 1); + } + + int arr2d[1][1] = {}; + { + multi_span s = arr2d; + CHECK(s.rank() == 2); + } +} + +TEST_CASE("extent") +{ + { + multi_span s; + CHECK(s.extent() == 0); + CHECK(s.extent(0) == 0); + CHECK_THROWS_AS(s.extent(1), std::logic_error); +#ifdef CONFIRM_COMPILATION_ERRORS + CHECK(s.extent<1>() == 0); +#endif + } + + { + multi_span s; + CHECK(s.extent() == 0); + CHECK(s.extent(0) == 0); + CHECK_THROWS_AS(s.extent(1), std::logic_error); + } + + { + int arr2d[1][2] = {}; + + multi_span s = arr2d; + CHECK(s.extent() == 1); + CHECK(s.extent<0>() == 1); + CHECK(s.extent<1>() == 2); + CHECK(s.extent(0) == 1); + CHECK(s.extent(1) == 2); + CHECK_THROWS_AS(s.extent(3), std::logic_error); + } + + { + int arr2d[1][2] = {}; + + multi_span s = arr2d; + CHECK(s.extent() == 0); + CHECK(s.extent<0>() == 0); + CHECK(s.extent<1>() == 2); + CHECK(s.extent(0) == 0); + CHECK(s.extent(1) == 2); + CHECK_THROWS_AS(s.extent(3), std::logic_error); + } +} + +TEST_CASE("operator_function_call") +{ + int arr[4] = {1, 2, 3, 4}; + + { + multi_span s = arr; + CHECK(s(0) == 1); + CHECK_THROWS_AS(s(5), std::logic_error); + } + + int arr2d[2][3] = {1, 2, 3, 4, 5, 6}; + + { + multi_span s = arr2d; + CHECK(s(0, 0) == 1); + CHECK(s(0, 1) == 2); + CHECK(s(1, 2) == 6); + } + + int arr3d[2][2][2] = {1, 2, 3, 4, 5, 6, 7, 8}; + + { + multi_span s = arr3d; + CHECK(s(0, 0, 0) == 1); + CHECK(s(1, 1, 1) == 8); + } +} + +TEST_CASE("comparison_operators") +{ + { + int arr[10][2]; + auto s1 = as_multi_span(arr); + multi_span s2 = s1; + + CHECK(s1 == s2); + + multi_span s3 = as_multi_span(s1, dim(20)); + CHECK((s3 == s2 && s3 == s1)); + } + + { + multi_span s1 = nullptr; + multi_span s2 = nullptr; + CHECK(s1 == s2); + CHECK(!(s1 != s2)); + CHECK(!(s1 < s2)); + CHECK(s1 <= s2); + CHECK(!(s1 > s2)); + CHECK(s1 >= s2); + CHECK(s2 == s1); + CHECK(!(s2 != s1)); + CHECK(!(s2 < s1)); + CHECK(s2 <= s1); + CHECK(!(s2 > s1)); + CHECK(s2 >= s1); + } + + { + int arr[] = {2, 1}; // bigger + + multi_span s1 = nullptr; + multi_span s2 = arr; + + CHECK(s1 != s2); + CHECK(s2 != s1); + CHECK(!(s1 == s2)); + CHECK(!(s2 == s1)); + CHECK(s1 < s2); + CHECK(!(s2 < s1)); + CHECK(s1 <= s2); + CHECK(!(s2 <= s1)); + CHECK(s2 > s1); + CHECK(!(s1 > s2)); + CHECK(s2 >= s1); + CHECK(!(s1 >= s2)); + } + + { + int arr1[] = {1, 2}; + int arr2[] = {1, 2}; + multi_span s1 = arr1; + multi_span s2 = arr2; + + CHECK(s1 == s2); + CHECK(!(s1 != s2)); + CHECK(!(s1 < s2)); + CHECK(s1 <= s2); + CHECK(!(s1 > s2)); + CHECK(s1 >= s2); + CHECK(s2 == s1); + CHECK(!(s2 != s1)); + CHECK(!(s2 < s1)); + CHECK(s2 <= s1); + CHECK(!(s2 > s1)); + CHECK(s2 >= s1); + } + + { + int arr[] = {1, 2, 3}; + + multi_span s1 = {&arr[0], 2}; // shorter + multi_span s2 = arr; // longer + + CHECK(s1 != s2); + CHECK(s2 != s1); + CHECK(!(s1 == s2)); + CHECK(!(s2 == s1)); + CHECK(s1 < s2); + CHECK(!(s2 < s1)); + CHECK(s1 <= s2); + CHECK(!(s2 <= s1)); + CHECK(s2 > s1); + CHECK(!(s1 > s2)); + CHECK(s2 >= s1); + CHECK(!(s1 >= s2)); + } + + { + int arr1[] = {1, 2}; // smaller + int arr2[] = {2, 1}; // bigger + + multi_span s1 = arr1; + multi_span s2 = arr2; + + CHECK(s1 != s2); + CHECK(s2 != s1); + CHECK(!(s1 == s2)); + CHECK(!(s2 == s1)); + CHECK(s1 < s2); + CHECK(!(s2 < s1)); + CHECK(s1 <= s2); + CHECK(!(s2 <= s1)); + CHECK(s2 > s1); + CHECK(!(s1 > s2)); + CHECK(s2 >= s1); + CHECK(!(s1 >= s2)); + } +} + +TEST_CASE("basics") +{ + auto ptr = as_multi_span(new int[10], 10); + fill(ptr.begin(), ptr.end(), 99); + for (int num : ptr) + { + CHECK(num == 99); + } + + delete[] ptr.data(); +} + +TEST_CASE("bounds_checks") +{ + int arr[10][2]; + auto av = as_multi_span(arr); + + fill(begin(av), end(av), 0); + + av[2][0] = 1; + av[1][1] = 3; + + // out of bounds + CHECK_THROWS_AS(av[1][3] = 3, std::logic_error); + CHECK_THROWS_AS((av[{1, 3}] = 3), std::logic_error); + + CHECK_THROWS_AS(av[10][2], std::logic_error); + CHECK_THROWS_AS((av[{10, 2}]), std::logic_error); + + CHECK_THROWS_AS(av[-1][0], std::logic_error); + CHECK_THROWS_AS((av[{-1, 0}]), std::logic_error); + + CHECK_THROWS_AS(av[0][-1], std::logic_error); + CHECK_THROWS_AS((av[{0, -1}]), std::logic_error); +} + +void overloaded_func(multi_span exp, + int expected_value) +{ + for (auto val : exp) + { + CHECK(val == expected_value); + } +} + +void overloaded_func(multi_span exp, + char expected_value) +{ + for (auto val : exp) + { + CHECK(val == expected_value); + } +} + +void fixed_func(multi_span exp, int expected_value) +{ + for (auto val : exp) + { + CHECK(val == expected_value); + } +} + +TEST_CASE("span_parameter_test") +{ + auto data = new int[4][3][5]; + + auto av = as_multi_span(data, 4); + + CHECK(av.size() == 60); + + fill(av.begin(), av.end(), 34); + + int count = 0; + for_each(av.rbegin(), av.rend(), [&](int val) { count += val; }); + CHECK(count == 34 * 60); + overloaded_func(av, 34); + + overloaded_func(as_multi_span(av, dim(4), dim(3), dim(5)), 34); + + // fixed_func(av, 34); + delete[] data; +} + +TEST_CASE("md_access") +{ + auto width = 5, height = 20; + + auto imgSize = width * height; + auto image_ptr = new int[narrow_cast(imgSize)][3]; + + // size check will be done + auto image_view = as_multi_span( + as_multi_span(image_ptr, imgSize), dim(height), dim(width), dim<3>()); + + iota(image_view.begin(), image_view.end(), 1); + + int expected = 0; + for (auto i = 0; i < height; i++) + { + for (auto j = 0; j < width; j++) + { + CHECK(expected + 1 == image_view[i][j][0]); + CHECK(expected + 2 == image_view[i][j][1]); + CHECK(expected + 3 == image_view[i][j][2]); + + auto val = image_view[{i, j, 0}]; + CHECK(expected + 1 == val); + val = image_view[{i, j, 1}]; + CHECK(expected + 2 == val); + val = image_view[{i, j, 2}]; + CHECK(expected + 3 == val); + + expected += 3; + } + } + + delete[] image_ptr; +} + +TEST_CASE("as_multi_span") +{ + { + int* arr = new int[150]; + + auto av = as_multi_span(arr, dim<10>(), dim(3), dim<5>()); + + fill(av.begin(), av.end(), 24); + overloaded_func(av, 24); + + delete[] arr; + + array stdarr{0}; + auto av2 = as_multi_span(stdarr); + overloaded_func(as_multi_span(av2, dim(1), dim<3>(), dim<5>()), 0); + + string str = "ttttttttttttttt"; // size = 15 + auto t = str.data(); + (void)t; + auto av3 = as_multi_span(str); + overloaded_func(as_multi_span(av3, dim(1), dim<3>(), dim<5>()), 't'); + } + + { + string str; + multi_span strspan = as_multi_span(str); + (void)strspan; + const string cstr; + multi_span cstrspan = as_multi_span(cstr); + (void)cstrspan; + } + + { + int a[3][4][5]; + auto av = as_multi_span(a); + const int(*b)[4][5]; + b = a; + auto bv = as_multi_span(b, 3); + + CHECK(av == bv); + + const std::array arr = {0.0, 0.0, 0.0}; + auto cv = as_multi_span(arr); + (void)cv; + + vector vec(3); + auto dv = as_multi_span(vec); + (void)dv; + +#ifdef CONFIRM_COMPILATION_ERRORS + auto dv2 = as_multi_span(std::move(vec)); +#endif + } +} + +TEST_CASE("empty_spans") +{ + { + multi_span empty_av(nullptr); + + CHECK(empty_av.bounds().index_bounds() == multi_span_index<1>{0}); + CHECK_THROWS_AS(empty_av[0], std::logic_error); + CHECK_THROWS_AS(empty_av.begin()[0], std::logic_error); + CHECK_THROWS_AS(empty_av.cbegin()[0], std::logic_error); + for (auto& v : empty_av) + { + (void)v; + CHECK(false); + } + } + + { + multi_span empty_av = {}; + CHECK(empty_av.bounds().index_bounds() == multi_span_index<1>{0}); + CHECK_THROWS_AS(empty_av[0], std::logic_error); + CHECK_THROWS_AS(empty_av.begin()[0], std::logic_error); + CHECK_THROWS_AS(empty_av.cbegin()[0], std::logic_error); + for (auto& v : empty_av) + { + (void)v; + CHECK(false); + } + } +} + +TEST_CASE("index_constructor") +{ + auto arr = new int[8]; + for (int i = 0; i < 4; ++i) + { + arr[2 * i] = 4 + i; + arr[2 * i + 1] = i; + } + + multi_span av(arr, 8); + + ptrdiff_t a[1] = {0}; + multi_span_index<1> i = a; + + CHECK(av[i] == 4); + + auto av2 = as_multi_span(av, dim<4>(), dim(2)); + ptrdiff_t a2[2] = {0, 1}; + multi_span_index<2> i2 = a2; + + CHECK(av2[i2] == 0); + CHECK(av2[0][i] == 4); + + delete[] arr; +} + +TEST_CASE("index_constructors"){{// components of the same type + multi_span_index<3> i1(0, 1, 2); +CHECK(i1[0] == 0); + +// components of different types +std::size_t c0 = 0; +std::size_t c1 = 1; +multi_span_index<3> i2(c0, c1, 2); +CHECK(i2[0] == 0); + +// from array +multi_span_index<3> i3 = {0, 1, 2}; +CHECK(i3[0] == 0); + +// from other index of the same size type +multi_span_index<3> i4 = i3; +CHECK(i4[0] == 0); + +// default +multi_span_index<3> i7; +CHECK(i7[0] == 0); + +// default +multi_span_index<3> i9 = {}; +CHECK(i9[0] == 0); +} + +{ + // components of the same type + multi_span_index<1> i1(0); + CHECK(i1[0] == 0); + + // components of different types + std::size_t c0 = 0; + multi_span_index<1> i2(c0); + CHECK(i2[0] == 0); + + // from array + multi_span_index<1> i3 = {0}; + CHECK(i3[0] == 0); + + // from int + multi_span_index<1> i4 = 0; + CHECK(i4[0] == 0); + + // from other index of the same size type + multi_span_index<1> i5 = i3; + CHECK(i5[0] == 0); + + // default + multi_span_index<1> i8; + CHECK(i8[0] == 0); + + // default + multi_span_index<1> i9 = {}; + CHECK(i9[0] == 0); +} + +#ifdef CONFIRM_COMPILATION_ERRORS +{ + multi_span_index<3> i1(0, 1); + multi_span_index<3> i2(0, 1, 2, 3); + multi_span_index<3> i3 = {0}; + multi_span_index<3> i4 = {0, 1, 2, 3}; + multi_span_index<1> i5 = {0, 1}; +} +#endif +} + +TEST_CASE("index_operations") +{ + ptrdiff_t a[3] = {0, 1, 2}; + ptrdiff_t b[3] = {3, 4, 5}; + multi_span_index<3> i = a; + multi_span_index<3> j = b; + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + + { + multi_span_index<3> k = i + j; + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + CHECK(k[0] == 3); + CHECK(k[1] == 5); + CHECK(k[2] == 7); + } + + { + multi_span_index<3> k = i * 3; + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + CHECK(k[0] == 0); + CHECK(k[1] == 3); + CHECK(k[2] == 6); + } + + { + multi_span_index<3> k = 3 * i; + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + CHECK(k[0] == 0); + CHECK(k[1] == 3); + CHECK(k[2] == 6); + } + + { + multi_span_index<2> k = detail::shift_left(i); + + CHECK(i[0] == 0); + CHECK(i[1] == 1); + CHECK(i[2] == 2); + CHECK(k[0] == 1); + CHECK(k[1] == 2); + } +} + +void iterate_second_column(multi_span av) +{ + auto length = av.size() / 2; + + // view to the second column + auto section = av.section({0, 1}, {length, 1}); + + CHECK(section.size() == length); + for (auto i = 0; i < section.size(); ++i) + { + CHECK(section[i][0] == av[i][1]); + } + + for (auto i = 0; i < section.size(); ++i) + { + auto idx = + multi_span_index<2>{i, 0}; // avoid braces inside the CHECK macro + CHECK(section[idx] == av[i][1]); + } + + CHECK(section.bounds().index_bounds()[0] == length); + CHECK(section.bounds().index_bounds()[1] == 1); + for (auto i = 0; i < section.bounds().index_bounds()[0]; ++i) + { + for (auto j = 0; j < section.bounds().index_bounds()[1]; ++j) + { + auto idx = + multi_span_index<2>{i, j}; // avoid braces inside the CHECK macro + CHECK(section[idx] == av[i][1]); + } + } + + auto check_sum = 0; + for (auto i = 0; i < length; ++i) + { + check_sum += av[i][1]; + } + + { + auto idx = 0; + auto sum = 0; + for (auto num : section) + { + CHECK(num == av[idx][1]); + sum += num; + idx++; + } + + CHECK(sum == check_sum); + } + { + auto idx = length - 1; + auto sum = 0; + for (auto iter = section.rbegin(); iter != section.rend(); ++iter) + { + CHECK(*iter == av[idx][1]); + sum += *iter; + idx--; + } + + CHECK(sum == check_sum); + } +} + +TEST_CASE("span_section_iteration") +{ + int arr[4][2] = {{4, 0}, {5, 1}, {6, 2}, {7, 3}}; + + // static bounds + { + multi_span av = arr; + iterate_second_column(av); + } + // first bound is dynamic + { + multi_span av = arr; + iterate_second_column(av); + } + // second bound is dynamic + { + multi_span av = arr; + iterate_second_column(av); + } + // both bounds are dynamic + { + multi_span av = arr; + iterate_second_column(av); + } +} + +TEST_CASE("dynamic_span_section_iteration") +{ + auto height = 4, width = 2; + auto size = height * width; + + auto arr = new int[narrow_cast(size)]; + for (auto i = 0; i < size; ++i) + { + arr[i] = i; + } + + auto av = as_multi_span(arr, size); + + // first bound is dynamic + { + multi_span av2 = + as_multi_span(av, dim(height), dim(width)); + iterate_second_column(av2); + } + // second bound is dynamic + { + multi_span av2 = + as_multi_span(av, dim(height), dim(width)); + iterate_second_column(av2); + } + // both bounds are dynamic + { + multi_span av2 = + as_multi_span(av, dim(height), dim(width)); + iterate_second_column(av2); + } + + delete[] arr; +} + +TEST_CASE("span_structure_size") +{ + double(*arr)[3][4] = new double[100][3][4]; + multi_span av1(arr, 10); + + struct EffectiveStructure + { + double* v1; + ptrdiff_t v2; + }; + CHECK(sizeof(av1) == sizeof(EffectiveStructure)); + + CHECK_THROWS_AS(av1[10][3][4], std::logic_error); + + multi_span av2 = + as_multi_span(av1, dim(5), dim<6>(), dim<4>()); + (void)av2; + + delete[] arr; +} + +TEST_CASE("fixed_size_conversions") +{ + int arr[] = {1, 2, 3, 4}; + + // converting to an multi_span from an equal size array is ok + multi_span av4 = arr; + CHECK(av4.length() == 4); + + // converting to dynamic_range a_v is always ok + { + multi_span av = av4; + (void)av; + } + { + multi_span av = arr; + (void)av; + } + +// initialization or assignment to static multi_span that REDUCES size is NOT ok +#ifdef CONFIRM_COMPILATION_ERRORS + { + multi_span av2 = arr; + } + { + multi_span av2 = av4; + } +#endif + + { + multi_span av = arr; + multi_span av2 = av; + (void)av2; + } + +#ifdef CONFIRM_COMPILATION_ERRORS + { + multi_span av = arr; + multi_span av2 = av.as_multi_span(dim<2>(), dim<2>()); + } +#endif + + { + multi_span av = arr; + multi_span av2 = as_multi_span(av, dim(2), dim(2)); + auto workaround_macro = [&]() { return av2[{1, 0}] == 2; }; + CHECK(workaround_macro()); + } + + // but doing so explicitly is ok + + // you can convert statically + { + multi_span av2 = {arr, 2}; + (void)av2; + } + { + multi_span av2 = av4.first<1>(); + (void)av2; + } + + // ...or dynamically + { + // NB: implicit conversion to multi_span from + // multi_span + multi_span av2 = av4.first(1); + (void)av2; + } + + // initialization or assignment to static multi_span that requires size + // INCREASE is not ok. + int arr2[2] = {1, 2}; + +#ifdef CONFIRM_COMPILATION_ERRORS + { + multi_span av4 = arr2; + } + { + multi_span av2 = arr2; + multi_span av4 = av2; + } +#endif + { + auto f = [&]() { + const multi_span av9 = {arr2, 2}; + (void)av9; + }; + CHECK_THROWS_AS(f(), std::logic_error); + } + + // this should fail - we are trying to assign a small dynamic a_v to a + // fixed_size larger one + multi_span av = arr2; + auto f = [&]() { + const multi_span av2 = av; + (void)av2; + }; + CHECK_THROWS_AS(f(), std::logic_error); +} + +TEST_CASE("iterator") +{ + int a[] = {1, 2, 3, 4}; + + { + multi_span av = a; + for (auto& n : av) + { + n = 1; + } + for (std::size_t i = 0; i < 4; ++i) + { + CHECK(a[i] == 1); + } + } +} + +#ifdef CONFIRM_COMPILATION_ERRORS +copy(src_span_static, dst_span_static); +#endif + diff --git a/test/src/strided_span_tests.cpp b/test/src/strided_span_tests.cpp new file mode 100644 index 0000000..5e3090b --- /dev/null +++ b/test/src/strided_span_tests.cpp @@ -0,0 +1,635 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Maintained by Jack Diver. +// +// Modified from :- +// +// github.com/microsoft/GSL +// +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +// blanket turn off warnings from CppCoreCheck from catch +// so people aren't annoyed by them when running the tool. +#pragma warning(disable : 26440 26426) // from catch deprecated +#endif + +#include + +// for multi_span, contiguous_span_iterator, dim +#include + +#include // for size_t +#include // for begin, end +#include // for iota +#include // for integral_constant<>::value, is_convertible +#include // for vector + +using namespace std; +using namespace stdex; + +namespace +{ +struct BaseClass +{ +}; +struct DerivedClass : BaseClass +{ +}; +} + +TEST_CASE("span_section_test") +{ + int a[30][4][5]; + + const auto av = as_multi_span(a); + const auto sub = av.section({15, 0, 0}, multi_span_index<3>{2, 2, 2}); + const auto subsub = sub.section({1, 0, 0}, multi_span_index<3>{1, 1, 1}); + (void) subsub; +} + +TEST_CASE("span_section") +{ + std::vector data(5 * 10); + std::iota(begin(data), end(data), 0); + const multi_span av = as_multi_span(multi_span{data}, dim<5>(), dim<10>()); + + const strided_span av_section_1 = av.section({1, 2}, {3, 4}); + CHECK(!av_section_1.empty()); + CHECK((av_section_1[{0, 0}] == 12)); + CHECK((av_section_1[{0, 1}] == 13)); + CHECK((av_section_1[{1, 0}] == 22)); + CHECK((av_section_1[{2, 3}] == 35)); + + const strided_span av_section_2 = av_section_1.section({1, 2}, {2, 2}); + CHECK(!av_section_2.empty()); + CHECK((av_section_2[{0, 0}] == 24)); + CHECK((av_section_2[{0, 1}] == 25)); + CHECK((av_section_2[{1, 0}] == 34)); +} + +TEST_CASE("strided_span_constructors") +{ + // Check stride constructor + { + int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + const int carr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + + strided_span sav1{arr, {{9}, {1}}}; // T -> T + CHECK(sav1.bounds().index_bounds() == multi_span_index<1>{9}); + CHECK(sav1.bounds().stride() == 1); + CHECK((sav1[0] == 1 && sav1[8] == 9)); + + strided_span sav2{carr, {{4}, {2}}}; // const T -> const T + CHECK(sav2.bounds().index_bounds() == multi_span_index<1>{4}); + CHECK(sav2.bounds().strides() == multi_span_index<1>{2}); + CHECK((sav2[0] == 1 && sav2[3] == 7)); + + strided_span sav3{arr, {{2, 2}, {6, 2}}}; // T -> const T + CHECK((sav3.bounds().index_bounds() == multi_span_index<2>{2, 2})); + CHECK((sav3.bounds().strides() == multi_span_index<2>{6, 2})); + CHECK((sav3[{0, 0}] == 1 && sav3[{0, 1}] == 3 && sav3[{1, 0}] == 7)); + } + + // Check multi_span constructor + { + int arr[] = {1, 2}; + + // From non-cv-qualified source + { + const multi_span src = arr; + + strided_span sav{src, {2, 1}}; + CHECK(sav.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav[1] == 2); + +#if defined(_MSC_VER) && _MSC_VER > 1800 + // strided_span sav_c{ {src}, {2, 1} }; + strided_span sav_c{multi_span{src}, + strided_bounds<1>{2, 1}}; +#else + strided_span sav_c{multi_span{src}, + strided_bounds<1>{2, 1}}; +#endif + CHECK(sav_c.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_c.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_c[1] == 2); + +#if defined(_MSC_VER) && _MSC_VER > 1800 + strided_span sav_v{src, {2, 1}}; +#else + strided_span sav_v{multi_span{src}, + strided_bounds<1>{2, 1}}; +#endif + CHECK(sav_v.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_v.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_v[1] == 2); + +#if defined(_MSC_VER) && _MSC_VER > 1800 + strided_span sav_cv{src, {2, 1}}; +#else + strided_span sav_cv{multi_span{src}, + strided_bounds<1>{2, 1}}; +#endif + CHECK(sav_cv.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_cv.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_cv[1] == 2); + } + + // From const-qualified source + { + const multi_span src{arr}; + + strided_span sav_c{src, {2, 1}}; + CHECK(sav_c.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_c.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_c[1] == 2); + +#if defined(_MSC_VER) && _MSC_VER > 1800 + strided_span sav_cv{src, {2, 1}}; +#else + strided_span sav_cv{multi_span{src}, + strided_bounds<1>{2, 1}}; +#endif + + CHECK(sav_cv.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_cv.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_cv[1] == 2); + } + + // From volatile-qualified source + { + const multi_span src{arr}; + + strided_span sav_v{src, {2, 1}}; + CHECK(sav_v.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_v.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_v[1] == 2); + +#if defined(_MSC_VER) && _MSC_VER > 1800 + strided_span sav_cv{src, {2, 1}}; +#else + strided_span sav_cv{multi_span{src}, + strided_bounds<1>{2, 1}}; +#endif + CHECK(sav_cv.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_cv.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_cv[1] == 2); + } + + // From cv-qualified source + { + const multi_span src{arr}; + + strided_span sav_cv{src, {2, 1}}; + CHECK(sav_cv.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav_cv.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav_cv[1] == 2); + } + } + + // Check const-casting constructor + { + int arr[2] = {4, 5}; + + const multi_span av(arr, 2); + multi_span av2{av}; + CHECK(av2[1] == 5); + + static_assert( + std::is_convertible, multi_span>::value, + "ctor is not implicit!"); + + const strided_span src{arr, {2, 1}}; + strided_span sav{src}; + CHECK(sav.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav.bounds().stride() == 1); + CHECK(sav[1] == 5); + + static_assert( + std::is_convertible, strided_span>::value, + "ctor is not implicit!"); + } + + // Check copy constructor + { + int arr1[2] = {3, 4}; + const strided_span src1{arr1, {2, 1}}; + strided_span sav1{src1}; + + CHECK(sav1.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav1.bounds().stride() == 1); + CHECK(sav1[0] == 3); + + int arr2[6] = {1, 2, 3, 4, 5, 6}; + const strided_span src2{arr2, {{3, 2}, {2, 1}}}; + strided_span sav2{src2}; + CHECK((sav2.bounds().index_bounds() == multi_span_index<2>{3, 2})); + CHECK((sav2.bounds().strides() == multi_span_index<2>{2, 1})); + CHECK((sav2[{0, 0}] == 1 && sav2[{2, 0}] == 5)); + } + + // Check const-casting assignment operator + { + int arr1[2] = {1, 2}; + int arr2[6] = {3, 4, 5, 6, 7, 8}; + + const strided_span src{arr1, {{2}, {1}}}; + strided_span sav{arr2, {{3}, {2}}}; + strided_span& sav_ref = (sav = src); + CHECK(sav.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav[0] == 1); + CHECK(&sav_ref == &sav); + } + + // Check copy assignment operator + { + int arr1[2] = {3, 4}; + int arr1b[1] = {0}; + const strided_span src1{arr1, {2, 1}}; + strided_span sav1{arr1b, {1, 1}}; + strided_span& sav1_ref = (sav1 = src1); + CHECK(sav1.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav1.bounds().strides() == multi_span_index<1>{1}); + CHECK(sav1[0] == 3); + CHECK(&sav1_ref == &sav1); + + const int arr2[6] = {1, 2, 3, 4, 5, 6}; + const int arr2b[1] = {0}; + const strided_span src2{arr2, {{3, 2}, {2, 1}}}; + strided_span sav2{arr2b, {{1, 1}, {1, 1}}}; + strided_span& sav2_ref = (sav2 = src2); + CHECK((sav2.bounds().index_bounds() == multi_span_index<2>{3, 2})); + CHECK((sav2.bounds().strides() == multi_span_index<2>{2, 1})); + CHECK((sav2[{0, 0}] == 1 && sav2[{2, 0}] == 5)); + CHECK(&sav2_ref == &sav2); + } +} + +TEST_CASE("strided_span_slice") +{ + std::vector data(5 * 10); + std::iota(begin(data), end(data), 0); + const multi_span src = + as_multi_span(multi_span{data}, dim<5>(), dim<10>()); + + const strided_span sav{src, {{5, 10}, {10, 1}}}; +#ifdef CONFIRM_COMPILATION_ERRORS + const strided_span csav{{src}, {{5, 10}, {10, 1}}}; +#endif + const strided_span csav{multi_span{src}, + {{5, 10}, {10, 1}}}; + + strided_span sav_sl = sav[2]; + CHECK(sav_sl[0] == 20); + CHECK(sav_sl[9] == 29); + + strided_span csav_sl = sav[3]; + CHECK(csav_sl[0] == 30); + CHECK(csav_sl[9] == 39); + + CHECK(sav[4][0] == 40); + CHECK(sav[4][9] == 49); +} + +TEST_CASE("strided_span_column_major") +{ + // strided_span may be used to accommodate more peculiar + // use cases, such as column-major multidimensional array + // (aka. "FORTRAN" layout). + + int cm_array[3 * 5] = {1, 4, 7, 10, 13, 2, 5, 8, 11, 14, 3, 6, 9, 12, 15}; + strided_span cm_sav{cm_array, {{5, 3}, {1, 5}}}; + + // Accessing elements + CHECK((cm_sav[{0, 0}] == 1)); + CHECK((cm_sav[{0, 1}] == 2)); + CHECK((cm_sav[{1, 0}] == 4)); + CHECK((cm_sav[{4, 2}] == 15)); + + // Slice + strided_span cm_sl = cm_sav[3]; + + CHECK(cm_sl[0] == 10); + CHECK(cm_sl[1] == 11); + CHECK(cm_sl[2] == 12); + + // Section + strided_span cm_sec = cm_sav.section({2, 1}, {3, 2}); + + CHECK((cm_sec.bounds().index_bounds() == multi_span_index<2>{3, 2})); + CHECK((cm_sec[{0, 0}] == 8)); + CHECK((cm_sec[{0, 1}] == 9)); + CHECK((cm_sec[{1, 0}] == 11)); + CHECK((cm_sec[{2, 1}] == 15)); +} + +TEST_CASE("strided_span_bounds") +{ + int arr[] = {0, 1, 2, 3}; + multi_span av(arr); + + { + // incorrect sections + + CHECK_THROWS_AS(av.section(0, 0)[0], std::logic_error); + CHECK_THROWS_AS(av.section(1, 0)[0], std::logic_error); + CHECK_THROWS_AS(av.section(1, 1)[1], std::logic_error); + + CHECK_THROWS_AS(av.section(2, 5), std::logic_error); + CHECK_THROWS_AS(av.section(5, 2), std::logic_error); + CHECK_THROWS_AS(av.section(5, 0), std::logic_error); + CHECK_THROWS_AS(av.section(0, 5), std::logic_error); + CHECK_THROWS_AS(av.section(5, 5), std::logic_error); + } + + { + // zero stride + strided_span sav{av, {{4}, {}}}; + CHECK(sav[0] == 0); + CHECK(sav[3] == 0); + CHECK_THROWS_AS(sav[4], std::logic_error); + } + + { + // zero extent + strided_span sav{av, {{}, {1}}}; + CHECK_THROWS_AS(sav[0], std::logic_error); + } + + { + // zero extent and stride + strided_span sav{av, {{}, {}}}; + CHECK_THROWS_AS(sav[0], std::logic_error); + } + + { + // strided array ctor with matching strided bounds + strided_span sav{arr, {4, 1}}; + CHECK(sav.bounds().index_bounds() == multi_span_index<1>{4}); + CHECK(sav[3] == 3); + CHECK_THROWS_AS(sav[4], std::logic_error); + } + + { + // strided array ctor with smaller strided bounds + strided_span sav{arr, {2, 1}}; + CHECK(sav.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav[1] == 1); + CHECK_THROWS_AS(sav[2], std::logic_error); + } + + { + // strided array ctor with fitting irregular bounds + strided_span sav{arr, {2, 3}}; + CHECK(sav.bounds().index_bounds() == multi_span_index<1>{2}); + CHECK(sav[0] == 0); + CHECK(sav[1] == 3); + CHECK_THROWS_AS(sav[2], std::logic_error); + } + + { + // bounds cross data boundaries - from static arrays + CHECK_THROWS_AS((strided_span{arr, {3, 2}}), std::logic_error); + CHECK_THROWS_AS((strided_span{arr, {3, 3}}), std::logic_error); + CHECK_THROWS_AS((strided_span{arr, {4, 5}}), std::logic_error); + CHECK_THROWS_AS((strided_span{arr, {5, 1}}), std::logic_error); + CHECK_THROWS_AS((strided_span{arr, {5, 5}}), std::logic_error); + } + + { + // bounds cross data boundaries - from array view + CHECK_THROWS_AS((strided_span{av, {3, 2}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av, {3, 3}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av, {4, 5}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av, {5, 1}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av, {5, 5}}), std::logic_error); + } + + { + // bounds cross data boundaries - from dynamic arrays + CHECK_THROWS_AS((strided_span{av.data(), 4, {3, 2}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av.data(), 4, {3, 3}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av.data(), 4, {4, 5}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av.data(), 4, {5, 1}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av.data(), 4, {5, 5}}), std::logic_error); + CHECK_THROWS_AS((strided_span{av.data(), 2, {2, 2}}), std::logic_error); + } + +#ifdef CONFIRM_COMPILATION_ERRORS + { + strided_span sav0{av.data(), {3, 2}}; + strided_span sav1{arr, {1}}; + strided_span sav2{arr, {1, 1, 1}}; + strided_span sav3{av, {1}}; + strided_span sav4{av, {1, 1, 1}}; + strided_span sav5{av.as_multi_span(dim<2>(), dim<2>()), {1}}; + strided_span sav6{av.as_multi_span(dim<2>(), dim<2>()), {1, 1, 1}}; + strided_span sav7{av.as_multi_span(dim<2>(), dim<2>()), + {{1, 1}, {1, 1}, {1, 1}}}; + + multi_span_index<1> index{0, 1}; + strided_span sav8{arr, {1, {1, 1}}}; + strided_span sav9{arr, {{1, 1}, {1, 1}}}; + strided_span sav10{av, {1, {1, 1}}}; + strided_span sav11{av, {{1, 1}, {1, 1}}}; + strided_span sav12{av.as_multi_span(dim<2>(), dim<2>()), {{1}, {1}}}; + strided_span sav13{av.as_multi_span(dim<2>(), dim<2>()), {{1}, {1, 1, 1}}}; + strided_span sav14{av.as_multi_span(dim<2>(), dim<2>()), {{1, 1, 1}, {1}}}; + } +#endif +} + +TEST_CASE("strided_span_type_conversion") +{ + int arr[] = {0, 1, 2, 3}; + multi_span av(arr); + + { + strided_span sav{av.data(), av.size(), {av.size() / 2, 2}}; +#ifdef CONFIRM_COMPILATION_ERRORS + strided_span lsav1 = sav.as_strided_span(); +#endif + } + { + strided_span sav{av, {av.size() / 2, 2}}; +#ifdef CONFIRM_COMPILATION_ERRORS + strided_span lsav1 = sav.as_strided_span(); +#endif + } +} + +TEST_CASE("empty_strided_spans") +{ + { + multi_span empty_av(nullptr); + strided_span empty_sav{empty_av, {0, 1}}; + + CHECK(empty_sav.bounds().index_bounds() == multi_span_index<1>{0}); + CHECK(empty_sav.empty()); + CHECK_THROWS_AS(empty_sav[0], std::logic_error); + CHECK_THROWS_AS(empty_sav.begin()[0], std::logic_error); + CHECK_THROWS_AS(empty_sav.cbegin()[0], std::logic_error); + + for (const auto& v : empty_sav) { + (void) v; + CHECK(false); + } + } + + { + strided_span empty_sav{nullptr, 0, {0, 1}}; + + CHECK(empty_sav.bounds().index_bounds() == multi_span_index<1>{0}); + CHECK_THROWS_AS(empty_sav[0], std::logic_error); + CHECK_THROWS_AS(empty_sav.begin()[0], std::logic_error); + CHECK_THROWS_AS(empty_sav.cbegin()[0], std::logic_error); + + for (const auto& v : empty_sav) { + (void) v; + CHECK(false); + } + } +} + +void iterate_every_other_element(multi_span av) +{ + // pick every other element + + auto length = av.size() / 2; +#if defined(_MSC_VER) && _MSC_VER > 1800 + auto bounds = strided_bounds<1>({length}, {2}); +#else + auto bounds = strided_bounds<1>(multi_span_index<1>{length}, multi_span_index<1>{2}); +#endif + strided_span strided(&av.data()[1], av.size() - 1, bounds); + + CHECK(strided.size() == length); + CHECK(strided.bounds().index_bounds()[0] == length); + for (auto i = 0; i < strided.size(); ++i) { + CHECK(strided[i] == av[2 * i + 1]); + } + + int idx = 0; + for (auto num : strided) { + CHECK(num == av[2 * idx + 1]); + idx++; + } +} + +TEST_CASE("strided_span_section_iteration") +{ + int arr[8] = {4, 0, 5, 1, 6, 2, 7, 3}; + + // static bounds + { + multi_span av(arr, 8); + iterate_every_other_element(av); + } + + // dynamic bounds + { + multi_span av(arr, 8); + iterate_every_other_element(av); + } +} + +TEST_CASE("dynamic_strided_span_section_iteration") +{ + auto arr = new int[8]; + for (int i = 0; i < 4; ++i) { + arr[2 * i] = 4 + i; + arr[2 * i + 1] = i; + } + + auto av = as_multi_span(arr, 8); + iterate_every_other_element(av); + + delete[] arr; +} + +void iterate_second_slice(multi_span av) +{ + const int expected[6] = {2, 3, 10, 11, 18, 19}; + auto section = av.section({0, 1, 0}, {3, 1, 2}); + + for (auto i = 0; i < section.extent<0>(); ++i) { + for (auto j = 0; j < section.extent<1>(); ++j) + for (auto k = 0; k < section.extent<2>(); ++k) { + auto idx = multi_span_index<3>{i, j, k}; // avoid braces in the CHECK macro + CHECK(section[idx] == expected[2 * i + 2 * j + k]); + } + } + + for (auto i = 0; i < section.extent<0>(); ++i) { + for (auto j = 0; j < section.extent<1>(); ++j) + for (auto k = 0; k < section.extent<2>(); ++k) + CHECK(section[i][j][k] == expected[2 * i + 2 * j + k]); + } + + int i = 0; + for (const auto num : section) { + CHECK(num == expected[i]); + i++; + } +} + +TEST_CASE("strided_span_section_iteration_3d") +{ + int arr[3][4][2]{}; + for (auto i = 0; i < 3; ++i) { + for (auto j = 0; j < 4; ++j) + for (auto k = 0; k < 2; ++k) arr[i][j][k] = 8 * i + 2 * j + k; + } + + { + multi_span av = arr; + iterate_second_slice(av); + } +} + +TEST_CASE("dynamic_strided_span_section_iteration_3d") +{ + const auto height = 12, width = 2; + const auto size = height * width; + + auto arr = new int[static_cast(size)]; + for (auto i = 0; i < size; ++i) { + arr[i] = i; + } + + { + auto av = as_multi_span(as_multi_span(arr, 24), dim<3>(), dim<4>(), dim<2>()); + iterate_second_slice(av); + } + + { + auto av = as_multi_span(as_multi_span(arr, 24), dim(3), dim<4>(), dim<2>()); + iterate_second_slice(av); + } + + { + auto av = as_multi_span(as_multi_span(arr, 24), dim<3>(), dim(4), dim<2>()); + iterate_second_slice(av); + } + + { + auto av = as_multi_span(as_multi_span(arr, 24), dim<3>(), dim<4>(), dim(2)); + iterate_second_slice(av); + } + delete[] arr; +} + diff --git a/test/src/tests.cpp b/test/src/tests.cpp new file mode 100644 index 0000000..58d8287 --- /dev/null +++ b/test/src/tests.cpp @@ -0,0 +1,3 @@ +#define CATCH_CONFIG_MAIN +#include + diff --git a/test/unix_makefile.mk b/test/unix_makefile.mk new file mode 100644 index 0000000..1291953 --- /dev/null +++ b/test/unix_makefile.mk @@ -0,0 +1,42 @@ +################################################################################ +# # +# This file is a basic unix makefile which runs all the multi_span tests. # +# Prefer to use the bazel build setup. # +# Usage: make -f unix_makefile.mk # +# # +################################################################################ + +EXE_NAME = tests + +SRC_DIR = src +OBJ_DIR = obj + +SRC_FILES = $(wildcard ${SRC_DIR}/*.cpp) +OBJ_FILES = $(patsubst ${SRC_DIR}/%.cpp, ${OBJ_DIR}/%.o,${SRC_FILES}) + +INCLUDES += -I../include +INCLUDES += -ICatch2/single_include + +CXXFLAGS += -g -std=c++14 -O3 -DMULTI_SPAN_THROW_ON_CONTRACT_VIOLATION +CXXFLAGS += -Wall -Wextra -pedantic -Wno-array-bounds + +LDFLAGS += -g -DMULTI_SPAN_THROW_ON_CONTRACT_VIOLATION + +.PHONY: directories + +all: directories ${EXE_NAME} + +directories: ${OBJ_DIR} + +$(OBJ_DIR): + mkdir -p ${OBJ_DIR} + +$(EXE_NAME): ${OBJ_FILES} + ${CXX} ${LDFLAGS} -o $@ $^ + +$(OBJ_DIR)/%.o: ${SRC_DIR}/%.cpp + ${CXX} ${CXXFLAGS} ${INCLUDES} -c -o $@ $< + +clean: + rm -rf ${EXE_NAME} ${OBJ_DIR}/*.o + diff --git a/third_party/BUILD b/third_party/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/third_party/BUILD.catch2 b/third_party/BUILD.catch2 new file mode 100644 index 0000000..a10a2f5 --- /dev/null +++ b/third_party/BUILD.catch2 @@ -0,0 +1,11 @@ +cc_library( + name = "catch2", + hdrs = [ + "single_include/catch.hpp", + ], + include_prefix = "catch2", + strip_include_prefix = "single_include", + visibility = [ + "//visibility:public", + ], +)