diff --git a/include/wtf/buffer/buffer_view.hpp b/include/wtf/buffer/buffer_view.hpp index 5c65fa4..a4aab9d 100644 --- a/include/wtf/buffer/buffer_view.hpp +++ b/include/wtf/buffer/buffer_view.hpp @@ -379,6 +379,13 @@ class BufferView { template friend class BufferView; + template + friend auto visit_contiguous_buffer_view(Visitor&& visitor, Args&&... args); + + holder_type& holder_() { return *m_pholder_; } + + const holder_type& holder_() const { return *m_pholder_; } + void valid_index_(size_type index) const { if(index >= size()) { throw std::out_of_range("BufferView: index out of range"); @@ -477,4 +484,39 @@ std::span contiguous_buffer_cast(BufferView& buffer) { return buffer.template value(); } +/** @brief Wraps the process of calling a visitor with zero or more + * BufferView objects. + * + * @relates BufferView + * + * @tparam TupleType A std::tuple of floating-point types to try. Must be + * explicitly provided by the user. + * @tparam Visitor The type of the visitor to call. Must be a callable object + * capable of accepting `std::span` objects for each + * possible T in @p TupleType. Will be inferred by the + * compiler. + * @tparam Args The types of the arguments to forward to the visitor. Each + * is expected to be downcastable to a ContiguousModel holding + * one of the types in @p TupleType. Will be inferred by the + * compiler. + * + * @param[in] visitor The visitor to call with the unwrapped std::span + * objects. + * @param[in] args The ContiguousModel objects to unwrap and pass to the + * visitor. + * + * @return The return value of calling @p visitor with the unwrapped + * std::span objects. + * + * @throw std::runtime_error if any of the @p args cannot be downcast to a + * ContiguousModel holding one of the types in + * @p TupleType. Strong throw guarantee. + * @throw ??? if calling @p visitor throws. Same throw guarantee. + */ +template +auto visit_contiguous_buffer_view(Visitor&& visitor, Args&&... args) { + return detail_::visit_contiguous_view_model( + std::forward(visitor), args.holder_()...); +} + } // namespace wtf::buffer diff --git a/include/wtf/buffer/detail_/buffer_view_holder.hpp b/include/wtf/buffer/detail_/buffer_view_holder.hpp index 1c602ec..9ae2eb0 100644 --- a/include/wtf/buffer/detail_/buffer_view_holder.hpp +++ b/include/wtf/buffer/detail_/buffer_view_holder.hpp @@ -141,6 +141,14 @@ class BufferViewHolder { */ const_rtti_reference type() const { return m_rtti_; } + /** @brief Are the held elements read-only? + * + * @return true if the held elements are read-only, false otherwise. + * + * @throw None No-throw guarantee. + */ + bool is_const() const noexcept { return is_const_(); } + /** @brief Are the elements in the buffer contiguous? * * This method indicates whether the held buffer stores its elements @@ -194,6 +202,9 @@ class BufferViewHolder { /// The size of the held buffer virtual size_type size_() const noexcept = 0; + /// Does the derived class hold read-only data + virtual bool is_const_() const noexcept = 0; + /// Does the derived class store the buffer contiguously in memory? virtual bool is_contiguous_() const = 0; diff --git a/include/wtf/buffer/detail_/contiguous_view_model.hpp b/include/wtf/buffer/detail_/contiguous_view_model.hpp index 0e5520d..58215ea 100644 --- a/include/wtf/buffer/detail_/contiguous_view_model.hpp +++ b/include/wtf/buffer/detail_/contiguous_view_model.hpp @@ -44,11 +44,11 @@ class ContiguousViewModel using const_model_type = ContiguousViewModel; /// Is *this aliasing a read-only buffer? - static constexpr bool is_const = std::is_const_v; + static constexpr bool m_is_const = std::is_const_v; /// The type of buffer *this is acting like using buffer_type = - std::conditional_t; + std::conditional_t; public: /// Type *this inherits from @@ -153,6 +153,30 @@ class ContiguousViewModel */ const_pointer data() const { return m_buffer_.data(); } + /** @brief Returns the wrapped data as a std::span. + * + * Starting with C++20 this is the preferred way to access raw contiguous + * buffers. + * + * @return A std::span wrapping the underlying buffer. + * + * @throw None No-throw guarantee. + */ + auto span() { return std::span(data(), this->size()); } + + /** @brief Returns the wrapped data as a std::span. + * + * This method is the same as the non-const version except that it ensures + * the resulting span is read-only. + * + * @return A std::span wrapping the underlying buffer. + * + * @throw None No-throw guarantee. + */ + auto span() const { + return std::span(data(), this->size()); + } + /** @brief Compares the elements in the buffer for exact equality. * * Value equal is defined as having the same elements in the same order. @@ -195,6 +219,9 @@ class ContiguousViewModel /// Calls span_type's size() size_type size_() const noexcept override { return m_buffer_.size(); } + /// *this is read-only if FloatType is const + bool is_const_() const noexcept override { return m_is_const; } + /// *this always store data contiguously bool is_contiguous_() const override { return true; } @@ -210,4 +237,42 @@ class ContiguousViewModel span_type m_buffer_; }; +/** @brief Wraps the process of calling a visitor with zero or more + * ContiguousViewModel objects. + * + * @relates ContiguousViewModel + * + * @tparam TupleType A std::tuple of floating-point types to try. Must be + * explicitly provided by the user. + * @tparam Visitor The type of the visitor to call. Must be a callable object + * capable of accepting `std::span` objects for each + * possible T in @p TupleType. Will be inferred by the + * compiler. + * @tparam Args The types of the arguments to forward to the visitor. Each + * is expected to be downcastable to a ContiguousModel holding + * one of the types in @p TupleType. Will be inferred by the + * compiler. + * + * @param[in] visitor The visitor to call with the unwrapped std::span + * objects. + * @param[in] args The ContiguousModel objects to unwrap and pass to the + * visitor. + * + * @return The return value of calling @p visitor with the unwrapped + * std::span objects. + * + * @throw std::runtime_error if any of the @p args cannot be downcast to a + * ContiguousModel holding one of the types in + * @p TupleType. Strong throw guarantee. + * @throw ??? if calling @p visitor throws. Same throw guarantee. + */ +template +auto visit_contiguous_view_model(Visitor&& visitor, Args&&... args) { + auto lambda = [&](auto&&... inner_args) { + return visitor(inner_args.span()...); + }; + return wtf::detail_::dispatch( + lambda, std::forward(args)...); +} + } // namespace wtf::buffer::detail_ diff --git a/tests/unit_tests/wtf/buffer/buffer_view.cpp b/tests/unit_tests/wtf/buffer/buffer_view.cpp index 01b81be..1ca9b1f 100644 --- a/tests/unit_tests/wtf/buffer/buffer_view.cpp +++ b/tests/unit_tests/wtf/buffer/buffer_view.cpp @@ -421,3 +421,138 @@ TEMPLATE_LIST_TEST_CASE("contiguous_buffer_cast(BufferView)", "[wtf]", REQUIRE(const_span[2] == three); REQUIRE(const_span.data() == cdata); } + +struct ConstVisitorConstSpan { + ConstVisitorConstSpan(float* pdataf, double* pdatad) : + pdataf_corr(pdataf), pdatad_corr(pdatad) {} + + auto operator()(std::span span) const { + REQUIRE(span.data() == pdataf_corr); + REQUIRE(span.size() == 3); + } + + auto operator()(std::span span) const { + REQUIRE(span.data() == pdatad_corr); + REQUIRE(span.size() == 3); + } + + auto operator()(std::span lhs, std::span rhs) const { + REQUIRE(lhs.data() == pdataf_corr); + REQUIRE(lhs.size() == 3); + REQUIRE(rhs.data() == pdatad_corr); + REQUIRE(rhs.size() == 3); + } + + template + auto operator()(std::span lhs, std::span rhs) const { + throw std::runtime_error("Only float, double supported"); + } + + float* pdataf_corr; + double* pdatad_corr; +}; + +struct VisitorConstSpan { + VisitorConstSpan(float* pdataf, double* pdatad) : + pdataf_corr(pdataf), pdatad_corr(pdatad) {} + + template + auto operator()(std::span span) { + if constexpr(std::is_same_v, float>) { + REQUIRE(span.data() == pdataf_corr); + m_called_float = true; + } else { + REQUIRE(span.data() == pdatad_corr); + m_called_double = true; + } + } + + template + auto operator()(std::span span) { + if constexpr(std::is_same_v, float>) { + REQUIRE(span.data() == pdataf_corr); + m_called_cfloat = true; + } else { + REQUIRE(span.data() == pdatad_corr); + m_called_cdouble = true; + } + REQUIRE(span.size() == 3); + } + + auto operator()(std::span lhs, std::span rhs) { + REQUIRE(lhs.data() == pdataf_corr); + REQUIRE(lhs.size() == 3); + REQUIRE(rhs.data() == pdatad_corr); + REQUIRE(rhs.size() == 3); + m_called_float_double = true; + } + + template + auto operator()(std::span lhs, std::span rhs) const { + throw std::runtime_error("Only float, double supported"); + } + + bool m_called_float = false; + bool m_called_cfloat = false; + bool m_called_double = false; + bool m_called_cdouble = false; + bool m_called_float_double = false; + + float* pdataf_corr; + double* pdatad_corr; +}; + +TEST_CASE("visit_contiguous_buffer_view") { + std::vector valf{1.0, 2.0, 3.0}; + std::vector vald{1.0, 2.0, 3.0}; + auto pdataf = valf.data(); + auto pdatad = vald.data(); + + ConstVisitorConstSpan visitor(pdataf, pdatad); + VisitorConstSpan visitor2(pdataf, pdatad); + + BufferView modelf(pdataf, valf.size()); + BufferView modeld(pdatad, vald.size()); + + using type_tuple = std::tuple; + + SECTION("one argument") { + SECTION("call span/ span overloads") { + visit_contiguous_buffer_view(visitor, modelf); + visit_contiguous_buffer_view(visitor, + std::as_const(modelf)); + visit_contiguous_buffer_view(visitor, modeld); + visit_contiguous_buffer_view(visitor, + std::as_const(modeld)); + } + + SECTION("call span overload") { + const auto& cmodelf = modelf; + visit_contiguous_buffer_view(visitor2, cmodelf); + REQUIRE(visitor2.m_called_cfloat); + REQUIRE_FALSE(visitor2.m_called_float); + + const auto& cmodeld = modeld; + visit_contiguous_buffer_view(visitor2, cmodeld); + REQUIRE(visitor2.m_called_cdouble); + REQUIRE_FALSE(visitor2.m_called_double); + } + + SECTION("calls span overload") { + visit_contiguous_buffer_view(visitor2, modelf); + REQUIRE_FALSE(visitor2.m_called_cfloat); + REQUIRE(visitor2.m_called_float); + + visit_contiguous_buffer_view(visitor2, modeld); + REQUIRE_FALSE(visitor2.m_called_cdouble); + REQUIRE(visitor2.m_called_double); + } + } + + SECTION("Two arguments") { + visit_contiguous_buffer_view(visitor, modelf, modeld); + + visit_contiguous_buffer_view(visitor2, modelf, modeld); + REQUIRE(visitor2.m_called_float_double); + } +} diff --git a/tests/unit_tests/wtf/buffer/detail_/contiguous_view_model.cpp b/tests/unit_tests/wtf/buffer/detail_/contiguous_view_model.cpp index d2ea226..7dfab2f 100644 --- a/tests/unit_tests/wtf/buffer/detail_/contiguous_view_model.cpp +++ b/tests/unit_tests/wtf/buffer/detail_/contiguous_view_model.cpp @@ -206,3 +206,63 @@ TEMPLATE_LIST_TEST_CASE("ContiguousViewModel", "[wtf]", all_fp_types) { REQUIRE_FALSE(const_model.are_equal(other_const_model)); } } + +struct CheckVisitContiguousViewModel { + CheckVisitContiguousViewModel(float* pdataf, double* pdatad) : + pdataf_corr(pdataf), pdatad_corr(pdatad) {} + + auto operator()(std::span span) const { + REQUIRE(span.data() == pdataf_corr); + REQUIRE(span.size() == 3); + } + + auto operator()(std::span span) const { + REQUIRE(span.data() == pdatad_corr); + REQUIRE(span.size() == 3); + } + + auto operator()(std::span lhs, std::span rhs) const { + REQUIRE(lhs.data() == pdataf_corr); + REQUIRE(lhs.size() == 3); + REQUIRE(rhs.data() == pdatad_corr); + REQUIRE(rhs.size() == 3); + } + + template + auto operator()(std::span lhs, std::span rhs) const { + throw std::runtime_error("Only float, double supported"); + } + + float* pdataf_corr; + double* pdatad_corr; +}; + +TEST_CASE("visit_contiguous_view_model") { + std::vector valf{1.0, 2.0, 3.0}; + std::vector vald{1.0, 2.0, 3.0}; + auto pdataf = valf.data(); + auto pdatad = vald.data(); + + CheckVisitContiguousViewModel visitor(pdataf, pdatad); + + ContiguousViewModel modelf(pdataf, valf.size()); + ContiguousViewModel modeld(pdatad, vald.size()); + ContiguousViewModel const_modelf(pdataf, valf.size()); + ContiguousViewModel const_modeld(pdatad, vald.size()); + + using type_tuple = std::tuple; + + SECTION("one argument") { + visit_contiguous_view_model(visitor, modelf); + visit_contiguous_view_model(visitor, modeld); + visit_contiguous_view_model(visitor, const_modelf); + visit_contiguous_view_model(visitor, const_modeld); + } + + SECTION("Two arguments") { + visit_contiguous_view_model(visitor, modelf, modeld); + REQUIRE_THROWS_AS(visit_contiguous_view_model( + visitor, const_modelf, const_modeld), + std::runtime_error); + } +}