diff --git a/include/wtf/buffer/detail_/buffer_holder.hpp b/include/wtf/buffer/detail_/buffer_holder.hpp index 40f6ec9..5249538 100644 --- a/include/wtf/buffer/detail_/buffer_holder.hpp +++ b/include/wtf/buffer/detail_/buffer_holder.hpp @@ -36,11 +36,14 @@ class BufferHolder { /// Type of a pointer to *this using holder_pointer = std::unique_ptr; + /// Type of an element of *this + using value_type = fp::Float; + /// Type of a view to an element of *this - using view_type = fp::FloatView; + using view_type = fp::FloatView; /// Type of a const view to an element of *this - using const_view_type = fp::FloatView; + using const_view_type = fp::FloatView; /// Type used to describe the RTTI of the held buffer using rtti_type = rtti::TypeInfo; @@ -52,13 +55,13 @@ class BufferHolder { using size_type = std::size_t; /// Type of a view that would act like *this - using buffer_view_holder = BufferViewHolder; + using buffer_view_holder = BufferViewHolder; /// Type of a pointer to a buffer_view_holder object using buffer_view_holder_pointer = std::unique_ptr; /// Type of a view that would act like a const version of *this - using const_buffer_view_holder = BufferViewHolder; + using const_buffer_view_holder = BufferViewHolder; /// Type of a pointer to a const_buffer_view_holder object using const_buffer_view_holder_pointer = diff --git a/include/wtf/buffer/float_buffer.hpp b/include/wtf/buffer/float_buffer.hpp index 0eb9dda..7b0269b 100644 --- a/include/wtf/buffer/float_buffer.hpp +++ b/include/wtf/buffer/float_buffer.hpp @@ -37,13 +37,14 @@ class FloatBuffer { /// Pull in types from the holder_type ///@{ using holder_pointer = typename holder_type::holder_pointer; + using value_type = typename holder_type::value_type; using size_type = typename holder_type::size_type; using view_type = typename holder_type::view_type; using const_view_type = typename holder_type::const_view_type; ///@} - using buffer_view = BufferView; - using const_buffer_view = BufferView; + using buffer_view = BufferView; + using const_buffer_view = BufferView; // ------------------------------------------------------------------------- // Ctors and assignment operators @@ -58,6 +59,24 @@ class FloatBuffer { */ FloatBuffer() noexcept = default; + /** @brief Creates a FloatBuffer from a list of specified values. + * + * @tparam T The type of floating-point value being held. Must satisfy the + * concepts::FloatingPoint concept. + * + * This ctor will copy the elements in @p buffer into a std::vector and + * then invoke the std::vector-based ctor to create the FloatBuffer. See + * that ctor for more details. + * + * @param[in] buffer The initial values for the buffer. + * + * @throw std::bad_alloc if creating the held buffer fails. Strong throw + * guarantee. + */ + template + explicit FloatBuffer(std::initializer_list buffer) : + FloatBuffer(std::vector(buffer)) {} + /** @brief Creates a FloatBuffer from a std::vector. * * @tparam T The type of floating-point value being held. Must satisfy @@ -356,6 +375,70 @@ auto make_float_buffer(Args&&... args) { return FloatBuffer(std::forward(args)...); } +/** @brief Wraps the process of making a FloatBuffer from a vector of Float + * objects. + * + * @tparam TupleType A std::tuple of floating-point types to try. Must be + * provided by the caller. + * + * @note Presently this method is restricted to Float objects which all + * contain the same type. Future versions may relax this restriction. + * + * This function creates a FloatBuffer object by unwrapping each of the + * provided Float objects and storing the unwrapped values in a FloatBuffer. + * + * @param[in] buffer The vector of Float objects to make the FloatBuffer from. + * + * @return The FloatBuffer created by unwrapping the Float objects + * in @p buffer. + * + * @throws std::runtime_error if the elements of @p buffer can not all be + * unwrapped to the same type. Strong throw + * guarantee. + */ +template +auto make_float_buffer(std::vector buffer) { + FloatBuffer rv; + if(buffer.size() == 0) return rv; + auto initializer = [&](auto value0) { + using T = std::decay_t; + std::vector temp_buffer(buffer.size()); + rv = FloatBuffer(std::move(temp_buffer)); + rv.at(0) = value0; + }; + fp::visit_float(initializer, buffer[0]); + for(std::size_t i = 1; i < buffer.size(); ++i) { + auto appender = [&](auto value) { rv.at(i) = value; }; + fp::visit_float(appender, buffer[i]); + } + return rv; +} + +/** @brief Wraps the process of making a FloatBuffer from an initializer list of + * Float objects. + * + * @tparam TupleType A std::tuple of floating-point types to try. Must be + * provided by the caller. + * + * This function uses the initializer list to create a std::vector of Float + * objects and then calls the std::vector-based make_float_buffer function. + * See that function for more details. + * + * @param[in] buffer The initializer list of Float objects to make the + * FloatBuffer from. + * + * @return The FloatBuffer created by unwrapping the Float objects. + * + * @throws std::bad_alloc if creating the std::vector fails. Strong throw + * guarantee. + * @throws ??? if the vector-based overload throws. Same throw guarantee. + */ +template +auto make_float_buffer(std::initializer_list buffer) { + return make_float_buffer( + std::vector(buffer)); +} + template std::span contiguous_buffer_cast(FloatBuffer& buffer) { if(!buffer.is_contiguous()) { diff --git a/include/wtf/fp/detail_/float_holder.hpp b/include/wtf/fp/detail_/float_holder.hpp index 609c3fb..0c5c948 100644 --- a/include/wtf/fp/detail_/float_holder.hpp +++ b/include/wtf/fp/detail_/float_holder.hpp @@ -163,6 +163,13 @@ class FloatHolder { */ const_type_info_reference type() const { return m_type_; } + /** @brief Is the type of the type-erased object read-only? + * + * @return True if the type of the type-erased object is const-qualified, + * and false otherwise. + */ + bool is_const() const { return is_const_(); } + protected: /// Initializes *this with the given type info object explicit FloatHolder(type_info ti) : m_type_(std::move(ti)) {} @@ -177,6 +184,9 @@ class FloatHolder { /// Creates a read-only view of the held floating-point value virtual const_float_view_type* as_view_() const = 0; + /// True if the held value is const, false otherwise + virtual bool is_const_() const = 0; + /// Base checked that types are equal, derived need only change values virtual void change_value_(const FloatHolder& other) = 0; diff --git a/include/wtf/fp/detail_/float_model.hpp b/include/wtf/fp/detail_/float_model.hpp index b8122a1..d6d0d1e 100644 --- a/include/wtf/fp/detail_/float_model.hpp +++ b/include/wtf/fp/detail_/float_model.hpp @@ -181,6 +181,9 @@ class FloatModel : public FloatHolder { return new const_view_type(data()); } + /// Implements is_const by checking if FloatType is const + bool is_const_() const override { return std::is_const_v; } + /// Implements FloatHolder::change_value by downcasting and calling /// set_value void change_value_(const FloatHolder& other) override { @@ -204,4 +207,23 @@ class FloatModel : public FloatHolder { value_type value_; }; +/** @brief Wraps the process of visiting zero or more FloatModel objects via + * FloatHolder references. + * + * @relates FloatModel + * + * @tparam TupleType A tuple of floating-point types to try. + * @tparam Visitor The type of the visitor being invoked. + * @tparam Args The cv-qualified FloatHolder objects to downcast. + */ +template +auto visit_float_model(Visitor&& visitor, Args&&... args) { + auto lambda = [&](auto&&... inner_args) { + return visitor(*inner_args.data()...); + }; + + return wtf::detail_::dispatch( + lambda, std::forward(args)...); +} + } // namespace wtf::fp::detail_ diff --git a/include/wtf/fp/float.hpp b/include/wtf/fp/float.hpp index b000078..12d5fd2 100644 --- a/include/wtf/fp/float.hpp +++ b/include/wtf/fp/float.hpp @@ -202,9 +202,15 @@ class Float { bool operator!=(const Float& other) const { return !(*this == other); } private: + template + friend auto visit_float(Visitor&& visitor, Args&&... args); + template friend Float make_float(T value); + auto& holder_() { return *m_holder_; } + const auto& holder_() const { return *m_holder_; } + template requires concepts::UnmodifiedFloatingPoint> friend T float_cast(Float& f); @@ -328,4 +334,29 @@ IGNORE_DANGLING_REFERENCE T float_cast(Float& f) { } return *pderived->data(); } + +/** @brief Wraps the process of visiting zero or more Float objects. + * + * @relates Float + * + * @tparam TupleType A std::tuple of floating-point types to try. Must be + * provided by the caller. + * @tparam Visitor The type of the visitor to call. Must be a callable object. + * Will be inferred by the compiler. + * @tparam Args The cv-qualified Float objects to type-restore. Will be + * inferred by the compiler. + * + * The visitor should have the signature: `R(T, U, ...)` where T, U, ... are + * types found in @p TupleType and R is the return type (which may be void). + * T, U, ... may be cv-qualified and may repeat types. + * + * @return The result of invoking @p visitor with the type-restored Float + * objects. + */ +template +auto visit_float(Visitor&& visitor, Args&&... args) { + return visit_float_model(std::forward(visitor), + (args.holder_())...); +}; + } // namespace wtf::fp diff --git a/tests/unit_tests/wtf/buffer/float_buffer.cpp b/tests/unit_tests/wtf/buffer/float_buffer.cpp index 49385df..4947588 100644 --- a/tests/unit_tests/wtf/buffer/float_buffer.cpp +++ b/tests/unit_tests/wtf/buffer/float_buffer.cpp @@ -36,6 +36,14 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { SECTION("ctors and assignment") { SECTION("default ctor") { REQUIRE(defaulted.size() == 0); } + SECTION("Initializer list of typed floats") { + FloatBuffer buf_from_init_list{one, two, three}; + REQUIRE(buf_from_init_list.size() == 3); + REQUIRE(buf_from_init_list.at(0) == one); + REQUIRE(buf_from_init_list.at(1) == two); + REQUIRE(buf_from_init_list.at(2) == three); + } + SECTION("By vector") { FloatBuffer buf_from_vector(val); REQUIRE(buf_from_vector.size() == 3); @@ -229,7 +237,7 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { } } -TEMPLATE_LIST_TEST_CASE("make_float_buffer", "[wtf]", all_fp_types) { +TEMPLATE_LIST_TEST_CASE("make_float_buffer(args...)", "[wtf]", all_fp_types) { using vector_type = std::vector; TestType one{1.0}, two{2.0}, three{3.0}; vector_type val{one, two, three}; @@ -241,6 +249,39 @@ TEMPLATE_LIST_TEST_CASE("make_float_buffer", "[wtf]", all_fp_types) { REQUIRE(buffer.at(2) == three); } +TEST_CASE("make_float_buffer(vector)") { + float one{1.0}; + auto wrap_one = wtf::fp::make_float(one); + double two{2.0}; + auto wrap_two = wtf::fp::make_float(two); + + FloatBuffer ones_corr(std::vector{one, one, one}); + std::vector wrapped_ones{wrap_one, wrap_one, wrap_one}; + FloatBuffer twos_corr(std::vector{two, two, two}); + std::vector wrapped_twos{wrap_two, wrap_two, wrap_two}; + std::vector mixed{wrap_one, wrap_two, wrap_one}; + + REQUIRE(make_float_buffer(wrapped_ones) == ones_corr); + REQUIRE(make_float_buffer(wrapped_twos) == twos_corr); + REQUIRE_THROWS_AS(make_float_buffer(mixed), + std::runtime_error); +} + +TEST_CASE("make_float_buffer(initializer_list)") { + float one{1.0}; + auto wrap_one = wtf::fp::make_float(one); + double two{2.0}; + auto wrap_two = wtf::fp::make_float(two); + + FloatBuffer ones_corr(std::vector{one, one, one}); + FloatBuffer twos_corr(std::vector{two, two, two}); + + REQUIRE(make_float_buffer({one, one, one}) == ones_corr); + REQUIRE(make_float_buffer({two, two, two}) == twos_corr); + REQUIRE_THROWS_AS(make_float_buffer({one, two, one}), + std::runtime_error); +} + TEMPLATE_LIST_TEST_CASE("contiguous_buffer_cast", "[wtf]", all_fp_types) { using vector_type = std::vector; TestType one{1.0}, two{2.0}, three{3.0}; diff --git a/tests/unit_tests/wtf/fp/detail_/float_model.cpp b/tests/unit_tests/wtf/fp/detail_/float_model.cpp index 172d13f..7595cc9 100644 --- a/tests/unit_tests/wtf/fp/detail_/float_model.cpp +++ b/tests/unit_tests/wtf/fp/detail_/float_model.cpp @@ -66,6 +66,8 @@ TEMPLATE_LIST_TEST_CASE("FloatModel", "[float_model]", test_wtf::all_fp_types) { REQUIRE(m.get_value() == new_val); } + SECTION("is_const") { REQUIRE_FALSE(m.is_const()); } + SECTION("data()") { auto p = m.data(); REQUIRE(*p == val); @@ -117,3 +119,16 @@ TEMPLATE_LIST_TEST_CASE("FloatModel", "[float_model]", test_wtf::all_fp_types) { REQUIRE_FALSE(m.are_equal(m4)); } } + +TEMPLATE_LIST_TEST_CASE("visit_float_model", "[float_model]", + test_wtf::all_fp_types) { + using float_t = TestType; + using model_t = FloatModel; + + float_t val = 2.71; + model_t m(val); + + auto visitor = [=](auto&& wrapped_value) { REQUIRE(wrapped_value == val); }; + + visit_float_model>(visitor, m); +} diff --git a/tests/unit_tests/wtf/fp/float.cpp b/tests/unit_tests/wtf/fp/float.cpp index eeb2c3b..d66d13a 100644 --- a/tests/unit_tests/wtf/fp/float.cpp +++ b/tests/unit_tests/wtf/fp/float.cpp @@ -145,3 +145,14 @@ TEST_CASE("float_cast", "[wtf]") { REQUIRE_THROWS_AS(float_cast(f), std::runtime_error); } + +TEMPLATE_LIST_TEST_CASE("visit_float", "[wtf]", test_wtf::all_fp_types) { + using float_t = TestType; + + float_t val = 2.71; + Float m(val); + + auto visitor = [=](auto&& wrapped_value) { REQUIRE(wrapped_value == val); }; + + visit_float>(visitor, m); +}