diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 4bf2811..e81d732 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -32,4 +32,4 @@ jobs: test_library: uses: NWChemEx/.github/.github/workflows/test_nwx_library.yaml@master with: - compilers: '["gcc-11", "clang-14"]' + compilers: '["gcc-14", "clang-18"]' diff --git a/cmake/catch2_tests_from_dir.cmake b/cmake/catch2_tests_from_dir.cmake index de331d0..782cfa2 100644 --- a/cmake/catch2_tests_from_dir.cmake +++ b/cmake/catch2_tests_from_dir.cmake @@ -16,14 +16,14 @@ include_guard() function(catch2_tests_from_dir ctfd_target_name ctfd_dir) file(GLOB_RECURSE ctfd_test_files CONFIGURE_DEPENDS ${ctfd_dir}/*.cpp) - + add_executable(${ctfd_target_name} ${ctfd_test_files}) - + target_link_libraries( ${ctfd_target_name} PRIVATE Catch2::Catch2WithMain ${ARGN}) - + add_test(NAME ${ctfd_target_name} COMMAND ${ctfd_target_name} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) -endfunction() \ No newline at end of file +endfunction() diff --git a/cmake/disable_in_source_builds.cmake b/cmake/disable_in_source_builds.cmake index 1f2f6af..a98cc9f 100644 --- a/cmake/disable_in_source_builds.cmake +++ b/cmake/disable_in_source_builds.cmake @@ -20,4 +20,4 @@ function(disable_in_source_builds) endif() endfunction() -disable_in_source_builds() \ No newline at end of file +disable_in_source_builds() diff --git a/docs/source/developer/operations.rst b/docs/source/developer/operations.rst index acafe64..2a6ba37 100644 --- a/docs/source/developer/operations.rst +++ b/docs/source/developer/operations.rst @@ -16,7 +16,7 @@ Performing Type-Safe Operations ############################### -A key challenge in the design of WTF is how to perform arbitrary numeric +A key challenge in the design of WTF is how to perform arbitrary numeric operations in a performant and type-safe manner. *************** @@ -33,10 +33,10 @@ something like: void run()(T val) { // do something with val } - }; + }; wtf::Buffer b = fxn_that_returns_a_buffer_of_wtf_floats(); - b.evaluate(MyFunctor{}); + b.evaluate(MyFunctor{}); can be implemented by having the ``evaluate`` function call a virtual function on the holder. The model class, then implements ``evaluate`` by calling the @@ -67,17 +67,17 @@ a time (the type of ``*this``), so we need a different solution. What won't work? ================ -At this point note that though ``std::variant`` is a common C++ solution for +At this point note that though ``std::variant`` is a common C++ solution for multiple dispatch. Instead of using the standard type-erasure pattern we'd hold the value in ``std::variant`` (the ``std::variant`` would live in the the class that is currently the interface or the class that is the holder and the model class would no longer be needed). However, this solution only works if we know ALL of the types ahead of time. Alternatively, we could template the -interface on the ``std::variant`` (or the types in the ``std::variant``) to +interface on the ``std::variant`` (or the types in the ``std::variant``) to avoid assuming a set of types, but this then destroys the type-erasure WTF is trying to implement since the interface is now templated! -Another common solution is the visitor pattern, but ultimately that +Another common solution is the visitor pattern, but ultimately that will require a class like: .. code-block:: c++ @@ -93,39 +93,39 @@ will require a class like: virtual void visit(long double, double) = 0; virtual void visit(long double, long double) = 0; }; - + // With some template trickery we could then get the following to work: Visitor& v = ...; // User-defined derived class passed by base wtf::Buffer val0; wtf::Buffer val1; - dispatch(v, val0, val1); + dispatch(v, val0, val1); -(n.b., for double dispatch we can make the visitor's interface linear in the -number of FP types by relying on polymorphism to work out one of the types; -however, it will still result in a combinatorial explosion for dispatching on -three or more inputs). This suffers from the same problem as ``std::variant``: -we don't want to assume a list of FP types (in fact, as the name ``std::visit`` -suggest, the two are related...). +(n.b., for double dispatch we can make the visitor's interface linear in the +number of FP types by relying on polymorphism to work out one of the types; +however, it will still result in a combinatorial explosion for dispatching on +three or more inputs). This suffers from the same problem as ``std::variant``: +we don't want to assume a list of FP types (in fact, as the name ``std::visit`` +suggest, the two are related...). -FWIW, there's a name for wanting to have multiple dispatch while not wanting to +FWIW, there's a name for wanting to have multiple dispatch while not wanting to assume a list of types: it's called the "expression problem". Solving the Expression Problem ============================== -At present, solving the expression problem requires a mix of storing RTTI -(runtime type information) and brute force looping over class hierarchies. +At present, solving the expression problem requires a mix of storing RTTI +(runtime type information) and brute force looping over class hierarchies. Making sure that all the edge cases are handled correctly is tricky and tedious. Note that this all needs to be done with template meta-programming, so it's also hideous to look at. So let's see if there is an existing solution that avoids us needing to reinvent the wheel. -One of the first solutions we found was the YOMM2 library (I'm guessing it -stands for yorel open multi-method; not sure what yorel means...). Yomm2 -provides a C++17 solution to the expression problem. Based on preliminary -investigations it suffers from weird scoping rules. More specifically, if you -define everything in one file it works, but if you try to split it up into +One of the first solutions we found was the YOMM2 library (I'm guessing it +stands for yorel open multi-method; not sure what yorel means...). Yomm2 +provides a C++17 solution to the expression problem. Based on preliminary +investigations it suffers from weird scoping rules. More specifically, if you +define everything in one file it works, but if you try to split it up into multiple files you can break it. Given that it's all native C++, the scope rules that Yomm2 ultimately follows are that of C++ itself. However, Yomm2 is a complicated web of C macros and template meta-programming making it quite @@ -140,11 +140,11 @@ finite set of floating point types and that they know what those types are. If that is the case, the user can provide us with the list of types they support when they want to dispatch. WTF can then use that list to create a series of ``std::variant`` objects for the operation, use ``std::visit`` to do the -multiple dispatch, and then return the result. By comparison, the usual -``std::variant`` solution forces the same set of types on all type-erased +multiple dispatch, and then return the result. By comparison, the usual +``std::variant`` solution forces the same set of types on all type-erased objects in the hierarchy and on all operations using those types. Our solution still allows the objects to erase arbitrary floating point types, but now restricts each execution of an operation to a set of types. Of note, each time an operation is invoked it can be invoked with a different set of types. Of course, if the held floating-point type is not convertible to one of the types -in the set, an exception will be thrown at runtime. \ No newline at end of file +in the set, an exception will be thrown at runtime. diff --git a/docs/source/developer/statement_of_need.rst b/docs/source/developer/statement_of_need.rst index 8869433..cbbcf0d 100644 --- a/docs/source/developer/statement_of_need.rst +++ b/docs/source/developer/statement_of_need.rst @@ -44,16 +44,16 @@ used. values, we just don't want to keep bringing it up. A fundamental problem in the software engineering of scientific libraries is -dealing with FP types. Historically, for simplicity many scientific libraries +dealing with FP types. Historically, for simplicity many scientific libraries have assumed ``double`` as the FP type at all interfaces. If the user is storing their data as ``float``, they must convert it to ``double`` to call the interface, and then convert the result back to ``float`` after the call. Admittedly, this is why many scientific libraries provide overloads for other FP types, but this in turn requires the developer to maintain one interface -per FP type. +per FP type. -C++ libraries can avoid the need to support multiple overloads by using -templates. As long as the user of the library is calling the function from C++, +C++ libraries can avoid the need to support multiple overloads by using +templates. As long as the user of the library is calling the function from C++, this solution works well. However, it is becoming increasingly important to be able to interface scientific software to other languages (e.g., Python). In most cases, interfacing is done through a C-like interface, precluding the use @@ -66,7 +66,7 @@ Problem Statement Scientific software developers increasingly need to support multiple FP types. At the same time, they want their software to be callable from multiple languages. Unfortunately, this precludes the use of templates at user-facing -interfaces. +interfaces. ***** Goals @@ -76,5 +76,5 @@ Goals arbitrary FP types. - Make it easy for users of WTF to compose algorithms with these abstractions. - Ensure that the user can extend WTF to support their own custom FP types - without needing to modify the WTF source. -- Ensure that the abstractions can be used in a performant manner. \ No newline at end of file + without needing to modify the WTF source. +- Ensure that the abstractions can be used in a performant manner. diff --git a/docs/source/developer/type_erasure.rst b/docs/source/developer/type_erasure.rst index 02989a8..078d52c 100644 --- a/docs/source/developer/type_erasure.rst +++ b/docs/source/developer/type_erasure.rst @@ -36,4 +36,4 @@ Type-erasure involves three classes. 3. The "Model" class. This is a class templated on the type of data being held. It derives from the "Holder" class and implements a model for data of type ``T``. In WTF these are ``wtf::detail::FloatModel`` and - ``wtf::detail::BufferModel``. \ No newline at end of file + ``wtf::detail::BufferModel``. diff --git a/include/wtf/buffer/buffer_view.hpp b/include/wtf/buffer/buffer_view.hpp index 50690c5..5c65fa4 100644 --- a/include/wtf/buffer/buffer_view.hpp +++ b/include/wtf/buffer/buffer_view.hpp @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include #include @@ -61,6 +62,32 @@ class BufferView { // Ctors and assignment operators // ------------------------------------------------------------------------- + /** @brief Creates a view of a null buffer. + * + * A null buffer has zero elements and can not be accessed. This ctor is + * used to create a null buffer. + * + * @throw None No-throw guarantee. + */ + BufferView() noexcept : BufferView(nullptr) {} + + /** @brief Implicitly converts FloatBuffer to BufferView. + * + * @tparam BufferType2 The type of the buffer being converted from. Must + * satisfy the concept FloatBuffer. + * + * This ctor is used to implicitly convert FloatBuffer objects into + * BufferView objects. + * + * @param[in] buffer The buffer to convert from. + * + * @throw std::bad_alloc if allocating the holder fails. Strong throw + * guarantee. + */ + template + requires(concepts::FloatBuffer>) + BufferView(BufferType2&& buffer) : BufferView(buffer.as_view()) {} + /** @brief Creates a BufferView from an existing buffer. * * @tparam T The type of floating-point value being held. Must satisfy @@ -77,6 +104,9 @@ class BufferView { explicit BufferView(T* pbuffer, size_type size) : m_pholder_(std::make_unique>(pbuffer, size)){}; + /// Other ctors create the holder and then dispatch to this ctor. + BufferView(holder_pointer pholder) : m_pholder_(std::move(pholder)) {} + /** @brief Conversion from mutable alias to a read-only alias. * * @tparam OtherFloat The type of floating-point value being held in @@ -94,7 +124,8 @@ class BufferView { template requires(is_const && !concepts::ConstQualified) BufferView(const BufferView& other) : - m_pholder_(other.m_pholder_->const_clone()) {} + m_pholder_(other.is_holding_() ? other.m_pholder_->const_clone() : + nullptr) {} /** @brief Creates a new BufferView as a copy of @p other. * @@ -109,7 +140,7 @@ class BufferView { * guarantee. */ BufferView(const BufferView& other) : - m_pholder_(other.m_pholder_->clone()) {} + m_pholder_(other.is_holding_() ? other.m_pholder_->clone() : nullptr) {} /** @brief Overrides the state of *this with a shallow copy of @p other. * @@ -125,7 +156,12 @@ class BufferView { * guarantee. */ BufferView& operator=(const BufferView& other) { - if(this != &other) { m_pholder_ = other.m_pholder_->clone(); } + if(this != &other) { + if(other.is_holding_()) + m_pholder_ = other.m_pholder_->clone(); + else + m_pholder_.reset(); + } return *this; } @@ -176,7 +212,10 @@ class BufferView { * @throw std::out_of_range if @p index is not less than size(). * Strong throw guarantee. */ - view_type at(size_type index) { return m_pholder_->at(index); } + view_type at(size_type index) { + valid_index_(index); + return m_pholder_->at(index); + } /** @brief Returns the element with offset @p index. * @@ -191,6 +230,7 @@ class BufferView { * throw guarantee. */ const_view_type at(size_type index) const { + valid_index_(index); return std::as_const(*m_pholder_).at(index); } @@ -204,7 +244,9 @@ class BufferView { * * @throw No-throw guarantee. */ - size_type size() const noexcept { return m_pholder_->size(); } + size_type size() const noexcept { + return is_holding_() ? m_pholder_->size() : 0; + } /** @brief Does the held buffer store the values contiguously? * @@ -218,7 +260,9 @@ class BufferView { * * @throw No-throw guarantee. */ - bool is_contiguous() const { return m_pholder_->is_contiguous(); } + bool is_contiguous() const { + return is_holding_() ? m_pholder_->is_contiguous() : true; + } /** @brief Determines if *this is value equal to @p other. * @@ -233,6 +277,11 @@ class BufferView { * @throw No-throw guarantee. */ bool operator==(const BufferView& other) const { + if(!is_holding_() && !other.is_holding_()) return true; + if(!is_holding_() || !other.is_holding_()) + return size() == other.size(); + + // Both are holding buffers, so compare the buffers return m_pholder_->are_equal(*other.m_pholder_); } @@ -253,6 +302,11 @@ class BufferView { template requires(!std::is_same_v) bool operator==(const BufferView& other) const { + if(!is_holding_() && !other.is_holding_()) return true; + if(!is_holding_() || !other.is_holding_()) + return size() == other.size(); + + // Both are holding buffers, so compare the buffers auto plhs = m_pholder_->const_clone(); auto prhs = other.m_pholder_->const_clone(); return plhs->are_equal(*prhs); @@ -294,6 +348,7 @@ class BufferView { */ template std::span value() { + if(!is_holding_()) return {}; auto& model = downcast_(); return std::span(model.data(), model.size()); } @@ -315,14 +370,21 @@ class BufferView { */ template std::span value() const { + if(!is_holding_()) return {}; const auto& model = downcast_(); return std::span(model.data(), model.size()); } private: - template + template friend class BufferView; + void valid_index_(size_type index) const { + if(index >= size()) { + throw std::out_of_range("BufferView: index out of range"); + } + } + /// Wraps converting to a contiguous model template auto& downcast_() { @@ -342,8 +404,8 @@ class BufferView { return const_cast(this)->downcast_(); } - /// Other ctors create the holder and then dispatch to this ctor. - BufferView(holder_pointer pholder) : m_pholder_(std::move(pholder)) {} + /// True if *this is aliasing a buffer and false otherwise + bool is_holding_() const noexcept { return m_pholder_ != nullptr; } /// The held type-erased buffer holder_pointer m_pholder_; diff --git a/include/wtf/buffer/detail_/buffer_holder.hpp b/include/wtf/buffer/detail_/buffer_holder.hpp index b48b06d..40f6ec9 100644 --- a/include/wtf/buffer/detail_/buffer_holder.hpp +++ b/include/wtf/buffer/detail_/buffer_holder.hpp @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include @@ -50,6 +51,19 @@ class BufferHolder { /// Type used for indexing using size_type = std::size_t; + /// Type of a view that would act like *this + 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; + + /// Type of a pointer to a const_buffer_view_holder object + using const_buffer_view_holder_pointer = + std::unique_ptr; + /// Default virtual destructor virtual ~BufferHolder() = default; @@ -65,6 +79,34 @@ class BufferHolder { */ holder_pointer clone() const { return holder_pointer(clone_()); } + /** @brief Explicitly creates a view of *this. + * + * FloatBuffer objects are implicitly convertible to BufferView + * objects, so that they can seamlessly interoperate. However, it is + * sometimes useful to be able to explicitly get a view of a FloatBuffer. + * This method does the heavy lifting for implementing that functionality. + * + * @return A unique_ptr to a BufferViewHolder that aliases *this. + * + * @throw std::bad_alloc if creating the view fails. Strong throw + * guarantee. + */ + auto as_view() { return buffer_view_holder_pointer(as_view_()); } + + /** @brief Explicitly creates a view of *this. + * + * This method is the const version of the non-const as_view() method. + * See the documentation of the non-const version for more details. + * + * @return A unique_ptr to a const BufferViewHolder that aliases *this. + * + * @throw std::bad_alloc if creating the view fails. Strong throw + * guarantee. + */ + auto as_view() const { + return const_buffer_view_holder_pointer(as_view_()); + } + /** @brief Retrieves the element with offset @p index. * * This method is used to access an element of the held buffer in a type- @@ -167,6 +209,12 @@ class BufferHolder { /// Clones *this polymorphically virtual holder_type* clone_() const = 0; + /// Creates a mutable view_holder to *this + virtual buffer_view_holder* as_view_() = 0; + + /// Creates a immutable view_holder to *this + virtual const_buffer_view_holder* as_view_() const = 0; + /// Base ensures in bounds, derived should just return the element virtual view_type at_(size_type index) = 0; diff --git a/include/wtf/buffer/detail_/contiguous_model.hpp b/include/wtf/buffer/detail_/contiguous_model.hpp index 1379628..6e695c2 100644 --- a/include/wtf/buffer/detail_/contiguous_model.hpp +++ b/include/wtf/buffer/detail_/contiguous_model.hpp @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include #include #include @@ -166,6 +167,18 @@ class ContiguousModel : public BufferHolder { /// Clones *this polymorphically holder_type* clone_() const override { return new ContiguousModel(*this); } + /// Creates a mutable view_holder to *this + buffer_view_holder* as_view_() override { + using view_model = ContiguousViewModel; + return new view_model(m_buffer_.data(), this->size()); + } + + /// Creates an immutable view_holder to *this + const_buffer_view_holder* as_view_() const override { + using const_view_model = ContiguousViewModel; + return new const_view_model(m_buffer_.data(), this->size()); + } + /// Calls vector_type's operator[] view_type at_(size_type index) override { return view_type(m_buffer_[index]); diff --git a/include/wtf/buffer/float_buffer.hpp b/include/wtf/buffer/float_buffer.hpp index d7c4f9f..e09ab2c 100644 --- a/include/wtf/buffer/float_buffer.hpp +++ b/include/wtf/buffer/float_buffer.hpp @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include @@ -41,10 +42,22 @@ class FloatBuffer { using const_view_type = typename holder_type::const_view_type; ///@} + using buffer_view = BufferView; + using const_buffer_view = BufferView; + // ------------------------------------------------------------------------- // Ctors and assignment operators // ------------------------------------------------------------------------- + /** @brief Creates an empty buffer. + * + * This ctor creates a FloatBuffer which contains zero elements and is + * capable of holding no elements. + * + * @throw None No throw guarantee. + */ + FloatBuffer() noexcept = default; + /** @brief Creates a FloatBuffer from a std::vector. * * @tparam T The type of floating-point value being held. Must satisfy @@ -95,7 +108,7 @@ class FloatBuffer { * guarantee. */ FloatBuffer(const FloatBuffer& other) : - m_pholder_(other.m_pholder_->clone()) {} + m_pholder_(other.is_holding_() ? other.m_pholder_->clone() : nullptr) {} /** @brief Overrides the state of *this with a deep copy of @p other. * @@ -134,6 +147,39 @@ class FloatBuffer { */ FloatBuffer& operator=(FloatBuffer&& other) noexcept = default; + /** @brief Explicitly converts *this into a BufferView. + * + * FloatBuffer objects are implicitly convertible to BufferView objects, + * so that APIs written in terms of BufferView objects can be used with + * both view and values. However, there are times when the user may want to + * be explicit about the conversion. This method allows for that. + * + * @return A BufferView aliasing the held buffer, or a null BufferView if + * *this is not holding a buffer. + * + * @throw std::bad_alloc if creating the BufferView fails. Strong throw + * guarantee. + */ + auto as_view() { + return buffer_view(is_holding_() ? m_pholder_->as_view() : nullptr); + } + + /** @brief Explicitly converts *this into a BufferView. + * + * This method is the same as the non-const version, but returns a read- + * only BufferView. See the non-const version for more details. + * + * @return A const BufferView aliasing the held buffer, or a null + * BufferView if *this is not holding a buffer. + * + * @throw std::bad_alloc if creating the BufferView fails. Strong throw + * guarantee. + */ + auto as_view() const { + return const_buffer_view( + is_holding_() ? std::as_const(*m_pholder_).as_view() : nullptr); + } + // ------------------------------------------------------------------------- // State accessors // ------------------------------------------------------------------------- @@ -154,7 +200,10 @@ class FloatBuffer { * @throw std::out_of_range if @p index is not less than size(). * Strong throw guarantee. */ - view_type at(size_type index) { return m_pholder_->at(index); } + view_type at(size_type index) { + in_range_(index); + return m_pholder_->at(index); + } /** @brief Returns the element with offset @p index. * @@ -169,6 +218,7 @@ class FloatBuffer { * throw guarantee. */ const_view_type at(size_type index) const { + in_range_(index); return std::as_const(*m_pholder_).at(index); } @@ -182,7 +232,9 @@ class FloatBuffer { * * @throw No-throw guarantee. */ - size_type size() const noexcept { return m_pholder_->size(); } + size_type size() const noexcept { + return is_holding_() ? m_pholder_->size() : 0; + } /** @brief Does the held buffer store the values contiguously? * @@ -196,7 +248,9 @@ class FloatBuffer { * * @throw No-throw guarantee. */ - bool is_contiguous() const { return m_pholder_->is_contiguous(); } + bool is_contiguous() const { + return is_holding_() ? m_pholder_->is_contiguous() : true; + } /** @brief Determines if *this is value equal to @p other. * @@ -211,7 +265,12 @@ class FloatBuffer { * @throw No-throw guarantee. */ bool operator==(const FloatBuffer& other) const { - return m_pholder_->are_equal(*other.m_pholder_); + if(is_holding_() && other.is_holding_()) + return m_pholder_->are_equal(*other.m_pholder_); + else if(!is_holding_() && !other.is_holding_()) + return true; + else // One is holding and the other is not, but may both be size 0 + return size() == other.size(); } /** @brief Is this buffer different from @p other? @@ -232,15 +291,21 @@ class FloatBuffer { template std::span value() { - return downcast_().span(); + return is_holding_() ? downcast_().span() : std::span{}; } template std::span value() const { - return downcast_().span(); + return is_holding_() ? downcast_().span() : std::span{}; } private: + void in_range_(size_type index) const { + if(index >= size()) { + throw std::out_of_range("Index out of range in FloatBuffer::at()"); + } + } + template friend auto visit_contiguous_buffer(Visitor&& visitor, Args&&... args); @@ -261,6 +326,9 @@ class FloatBuffer { return const_cast(this)->downcast_(); } + /// True if *this is actively type-erasing a buffer and false otherwise + bool is_holding_() const noexcept { return m_pholder_ != nullptr; } + /// Other ctors create the holder and then dispatch to this ctor. FloatBuffer(holder_pointer pholder) : m_pholder_(std::move(pholder)) {} @@ -342,8 +410,13 @@ FloatBuffer::FloatBuffer(BeginItr&& begin, EndItr&& end) : FloatBuffer( std::vector(std::forward(begin), std::forward(end))) {} -FloatBuffer& FloatBuffer::operator=(const FloatBuffer& other) { - if(this != &other) { m_pholder_ = other.m_pholder_->clone(); } +inline FloatBuffer& FloatBuffer::operator=(const FloatBuffer& other) { + if(this != &other) { + if(other.is_holding_()) + m_pholder_ = other.m_pholder_->clone(); + else + m_pholder_.reset(); + } return *this; } diff --git a/include/wtf/fp/detail_/float_holder.hpp b/include/wtf/fp/detail_/float_holder.hpp index 9101d46..609c3fb 100644 --- a/include/wtf/fp/detail_/float_holder.hpp +++ b/include/wtf/fp/detail_/float_holder.hpp @@ -15,6 +15,7 @@ */ #pragma once +#include #include namespace wtf::fp::detail_ { @@ -39,6 +40,18 @@ class FloatHolder { /// Read-only reference to type_info object using const_type_info_reference = const type_info&; + /// Type of a view of the held floating-point value + using float_view_type = FloatViewHolder; + + /// Type of a pointer to a float_view_type object + using float_view_pointer = std::unique_ptr; + + /// Type of a const view of the held floating-point value + using const_float_view_type = FloatViewHolder; + + /// Type of a pointer to a const_float_view_type object + using const_float_view_pointer = std::unique_ptr; + /// Default virtual destructor virtual ~FloatHolder() = default; @@ -52,6 +65,37 @@ class FloatHolder { */ holder_pointer clone() const { return holder_pointer(clone_()); } + /** @brief Creates a view of the held floating-point value. + * + * This method is used to create a FloatView object that aliases the + * floating-point value held by *this. This method is implemented by + * calling as_view_(). + * + * @return A unique_ptr to a FloatView aliasing the held floating-point + * value. + * + * @throw std::bad_alloc if there is a problem allocating the FloatView. + * Strong throw guarantee. + */ + float_view_pointer as_view() { return float_view_pointer(as_view_()); } + + /** @brief Creates a view of the held floating-point value. + * + * This method is the same as the non-const version, except that it + * returns a read-only view of the held floating-point value. This method + * is implemented by calling as_view_(). See the documentation for the + * non-const version for more details. + * + * @return A unique_ptr to a const FloatView aliasing the held floating- + * point value. + * + * @throw std::bad_alloc if there is a problem allocating the FloatView. + * Strong throw guarantee. + */ + const_float_view_pointer as_view() const { + return const_float_view_pointer(as_view_()); + } + /** @brief Wraps the process of changing the held value. * * This method is used to change the value held by *this to that held by @@ -127,6 +171,12 @@ class FloatHolder { /// Clones *this polymorphically virtual holder_type* clone_() const = 0; + /// Creates a view of the held floating-point value + virtual float_view_type* as_view_() = 0; + + /// Creates a read-only view of the held floating-point value + virtual const_float_view_type* as_view_() 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 f39af0b..b8122a1 100644 --- a/include/wtf/fp/detail_/float_model.hpp +++ b/include/wtf/fp/detail_/float_model.hpp @@ -18,6 +18,7 @@ #include // for std::move, std::swap #include #include +#include #include namespace wtf::fp::detail_ { @@ -168,6 +169,18 @@ class FloatModel : public FloatHolder { /// Implements clone() by making a new FloatModel with the copy holder_type* clone_() const override { return new FloatModel(*this); } + /// Implements as_view by making a FloatViewModel aliasing the held value + float_view_type* as_view_() override { + using view_type = FloatViewModel; + return new view_type(data()); + } + + /// Implements as_view const by making a const FloatViewModel + const_float_view_type* as_view_() const override { + using const_view_type = FloatViewModel; + return new const_view_type(data()); + } + /// Implements FloatHolder::change_value by downcasting and calling /// set_value void change_value_(const FloatHolder& other) override { diff --git a/include/wtf/fp/float.hpp b/include/wtf/fp/float.hpp index 3d59fc6..b000078 100644 --- a/include/wtf/fp/float.hpp +++ b/include/wtf/fp/float.hpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include namespace wtf::fp { @@ -36,6 +38,12 @@ class Float { /// Type of a pointer to the holder_type using holder_pointer = holder_type::holder_pointer; + /// Type of a view acting like *this + using view_type = FloatView; + + /// Type of a read-only view acting like *this + using const_view_type = FloatView; + // ------------------------------------------------------------------------- // Ctors and assignment operators // ------------------------------------------------------------------------- @@ -116,6 +124,36 @@ class Float { // Utility methods // ------------------------------------------------------------------------- + /** @brief Explicitly converts a Float object to a FloatView. + * + * Float objects are implicitly convertible to FloatView objects. This + * way APIs written to accept FloatView objects will also accept Float + * objects. However, sometimes it is useful to be able to explicitly + * convert a Float to a FloatView. This method provides that functionality. + * + * @return A FloatView that aliases the floating-point value held by *this. + * + * @throw std::bad_alloc if there is a problem allocating the FloatView. + * Strong throw guarantee. + */ + auto as_view() { return view_type(m_holder_->as_view()); } + + /** @brief Explicitly converts a const Float object to a const FloatView. + * + * This method is the const version of the non-const as_view() method. + * It provides the ability to explicitly convert a const Float to a + * FloatView. + * + * @return A FloatView that aliases the floating-point value + * held by *this. + * + * @throw std::bad_alloc if there is a problem allocating the FloatView. + * Strong throw guarantee. + */ + auto as_view() const { + return const_view_type(std::as_const(*m_holder_).as_view()); + } + /** @brief Exchanges the contents of *this with that of @p other. * * This method is used to exchange the stat of *this with the state of @@ -281,7 +319,7 @@ Float make_float(T value) { */ template requires concepts::UnmodifiedFloatingPoint> -T float_cast(Float& f) { +IGNORE_DANGLING_REFERENCE T float_cast(Float& f) { auto* p = f.m_holder_.get(); using derived_type = detail_::FloatModel>; auto pderived = dynamic_cast(p); diff --git a/include/wtf/fp/float_view.hpp b/include/wtf/fp/float_view.hpp index c28b21e..d725f16 100644 --- a/include/wtf/fp/float_view.hpp +++ b/include/wtf/fp/float_view.hpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include namespace wtf::fp { @@ -97,6 +97,22 @@ class FloatView { template FloatView(T& value) : m_pfloat_(std::make_unique>(&value)) {} + /// Common ctor used once the holder is created + FloatView(holder_pointer pfloat) : m_pfloat_(std::move(pfloat)) {} + + /** @brief Implicitly converts a Float object into a view. + * + * This ctor is implements the automatic conversion from Float to + * FloatView. + * + * @param[in] value The Float object to convert. The constructed FloatView + * will alias the state in @p value. + * + * @throw std::bad_alloc if memory for the internal holder can not be + * allocated. Strong throw guarantee. + */ + FloatView(FloatType& value) : FloatView(value.as_view()) {} + /** @brief Implicit conversion to a read-only alias. * * @tparam FloatType2 The type of Float being aliased by @p other. This @@ -186,6 +202,30 @@ class FloatView { template FloatView& operator=(T other); + /** @brief Changes the aliased value to @p other. + * + * @tparam OtherFloatType The type of the Float object being assigned from. + * Must satisfy the concepts::WTFFloat concept. + * + * This method does NOT make *this alias the value held by @p other (if you + * want that behavior do `*this = other.as_view()`). Instead, this method + * will change the value aliased by *this to be equal to the value wrapped + * by @p other. + * + * @param[in] other The other Float object to copy the value from. + * + * @return A reference to *this, after changing the aliased value. + * + * @throw std::invalid_argument if the type of the value held by @p other + * is not the same as that aliased by *this. + * Strong throw guarantee. + */ + template + FloatView& operator=(OtherFloatType& other) { + m_pfloat_->change_value(*(other.as_view().m_pfloat_)); + return *this; + } + // ------------------------------------------------------------------------- // Utility methods // ------------------------------------------------------------------------- @@ -320,9 +360,6 @@ class FloatView { template friend auto make_float_view(T& value); - /// Common ctor used once the holder is created - FloatView(holder_pointer pfloat) : m_pfloat_(std::move(pfloat)) {} - /// The holder object holder_pointer m_pfloat_; }; @@ -349,7 +386,7 @@ class FloatView { */ template requires concepts::FloatingPoint> -T float_cast(FloatView fview) { +IGNORE_DANGLING_REFERENCE T float_cast(FloatView fview) { return fview.template value(); } diff --git a/include/wtf/warnings.hpp b/include/wtf/warnings.hpp new file mode 100644 index 0000000..943e143 --- /dev/null +++ b/include/wtf/warnings.hpp @@ -0,0 +1,25 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#if defined(__clang__) +#define IGNORE_DANGLING_REFERENCE +#elif defined(__GNUC__) || defined(__GNUG__) +#define IGNORE_DANGLING_REFERENCE [[gnu::no_dangling]] +#else +#define IGNORE_DANGLING_REFERENCE +#endif diff --git a/include/wtf/wtf.hpp b/include/wtf/wtf.hpp index 7fd3ee3..713d0c4 100644 --- a/include/wtf/wtf.hpp +++ b/include/wtf/wtf.hpp @@ -21,6 +21,7 @@ #include #include #include +#include /** @brief Contains all public-facing APIs for the Weakly Typed Float library. * diff --git a/tests/test_wtf.hpp b/tests/test_wtf.hpp index ff2fc58..b75569e 100644 --- a/tests/test_wtf.hpp +++ b/tests/test_wtf.hpp @@ -59,4 +59,4 @@ namespace test_wtf { using all_fp_types = wtf::type_traits::tuple_append_t>; -} // namespace test_wtf \ No newline at end of file +} // namespace test_wtf diff --git a/tests/unit_tests/wtf/buffer/buffer_view.cpp b/tests/unit_tests/wtf/buffer/buffer_view.cpp index 8b08024..01b81be 100644 --- a/tests/unit_tests/wtf/buffer/buffer_view.cpp +++ b/tests/unit_tests/wtf/buffer/buffer_view.cpp @@ -16,6 +16,7 @@ #include "../../../test_wtf.hpp" #include +#include using namespace wtf::buffer; using namespace test_wtf; @@ -30,12 +31,29 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { vector_type val{one, two, three}; vector_type empty_vector{}; + view_type defaulted; view_type buffer(val.data(), 3); view_type empty(empty_vector.data(), 0); + + const_view_type const_defaulted; const_view_type const_buffer(val.data(), 3); const_view_type const_empty(empty_vector.data(), 0); SECTION("ctors and assignment") { + SECTION("default ctor") { + REQUIRE(defaulted.size() == 0); + REQUIRE(const_defaulted.size() == 0); + } + + SECTION("From FloatBuffer") { + FloatBuffer fb(val); + view_type from_fb(fb); + REQUIRE(from_fb.size() == 3); + REQUIRE(from_fb.at(0) == one); + REQUIRE(from_fb.at(1) == two); + REQUIRE(from_fb.at(2) == three); + } + SECTION("By pointer") { REQUIRE(buffer.size() == 3); REQUIRE(buffer.at(0) == one); @@ -69,6 +87,9 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { } SECTION("copy ctor") { + view_type copy_defaulted(defaulted); + REQUIRE(copy_defaulted.size() == 0); + view_type copy_buffer(buffer); REQUIRE(copy_buffer == buffer); @@ -89,25 +110,41 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { } SECTION("copy assignment") { - auto pempty_buffer = &(empty = buffer); - REQUIRE(empty == buffer); - REQUIRE(pempty_buffer == &empty); - - auto copy_elem0 = empty.at(0); - auto orig_elem0 = buffer.at(0); - auto& copy0 = float_cast(copy_elem0); - auto& orig0 = float_cast(orig_elem0); - REQUIRE(©0 == &orig0); // Ensure shallow copy - - auto pconst_empty_buffer = &(const_empty = const_buffer); - REQUIRE(const_empty == const_buffer); - REQUIRE(pconst_empty_buffer == &const_empty); - - auto copy_const_elem0 = const_empty.at(0); - auto orig_const_elem0 = const_buffer.at(0); - auto& copyc0 = float_cast(copy_const_elem0); - auto& origc0 = float_cast(orig_const_elem0); - REQUIRE(©c0 == &origc0); // Ensure shallow copy + SECTION("Copy from defaulted") { + auto pbuffer = &(buffer = defaulted); + REQUIRE(buffer.size() == 0); + REQUIRE(pbuffer == &buffer); + } + + SECTION("Copy to defaulted") { + auto pdefaulted = &(defaulted = buffer); + REQUIRE(defaulted == buffer); + REQUIRE(pdefaulted == &defaulted); + } + + SECTION("copy from non-empty") { + auto pempty_buffer = &(empty = buffer); + REQUIRE(empty == buffer); + REQUIRE(pempty_buffer == &empty); + + auto copy_elem0 = empty.at(0); + auto orig_elem0 = buffer.at(0); + auto& copy0 = float_cast(copy_elem0); + auto& orig0 = float_cast(orig_elem0); + REQUIRE(©0 == &orig0); // Ensure shallow copy + } + + SECTION("copy from const non-empty") { + auto pconst_empty_buffer = &(const_empty = const_buffer); + REQUIRE(const_empty == const_buffer); + REQUIRE(pconst_empty_buffer == &const_empty); + + auto copy_const_elem0 = const_empty.at(0); + auto orig_const_elem0 = const_buffer.at(0); + auto& copyc0 = float_cast(copy_const_elem0); + auto& origc0 = float_cast(orig_const_elem0); + REQUIRE(©c0 == &origc0); // Ensure shallow copy + } } SECTION("move ctor") { @@ -163,9 +200,11 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { const auto& celem0 = float_cast(const_buffer.at(0)); REQUIRE(&celem0 == val.data()); + REQUIRE_THROWS_AS(defaulted.at(0), std::out_of_range); REQUIRE_THROWS_AS(buffer.at(3), std::out_of_range); REQUIRE_THROWS_AS(empty.at(0), std::out_of_range); + REQUIRE_THROWS_AS(const_defaulted.at(0), std::out_of_range); REQUIRE_THROWS_AS(const_buffer.at(3), std::out_of_range); REQUIRE_THROWS_AS(const_empty.at(0), std::out_of_range); @@ -183,6 +222,7 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { const auto& celem0 = float_cast(cholder.at(0)); REQUIRE(&celem0 == val.data()); + const auto& cconst_defaulted = const_defaulted; const auto& cconst_holder = const_buffer; const auto& cconst_empty_holder = const_empty; REQUIRE(cconst_holder.at(0) == one); @@ -191,38 +231,53 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { const auto& ccelem0 = float_cast(cconst_holder.at(0)); REQUIRE(&ccelem0 == val.data()); + REQUIRE_THROWS_AS(std::as_const(defaulted).at(0), std::out_of_range); REQUIRE_THROWS_AS(cholder.at(3), std::out_of_range); REQUIRE_THROWS_AS(cempty_holder.at(0), std::out_of_range); + REQUIRE_THROWS_AS(cconst_defaulted.at(0), std::out_of_range); REQUIRE_THROWS_AS(cconst_holder.at(3), std::out_of_range); REQUIRE_THROWS_AS(cconst_empty_holder.at(0), std::out_of_range); } SECTION("size()") { + REQUIRE(defaulted.size() == 0); REQUIRE(buffer.size() == 3); REQUIRE(empty.size() == 0); + REQUIRE(const_defaulted.size() == 0); REQUIRE(const_buffer.size() == 3); REQUIRE(const_empty.size() == 0); } SECTION("is_contiguous()") { + REQUIRE(defaulted.is_contiguous()); REQUIRE(buffer.is_contiguous()); REQUIRE(empty.is_contiguous()); + REQUIRE(const_defaulted.is_contiguous()); REQUIRE(const_buffer.is_contiguous()); REQUIRE(const_empty.is_contiguous()); } SECTION("operator==") { // Same contents + REQUIRE(defaulted == view_type{}); REQUIRE(buffer == view_type(val.data(), 3)); REQUIRE(empty == view_type(empty_vector.data(), 0)); + + REQUIRE(const_defaulted == const_view_type{}); REQUIRE(const_buffer == const_view_type(val.data(), 3)); REQUIRE(const_empty == const_view_type(empty_vector.data(), 0)); + // Empty equals defaulted + REQUIRE(defaulted == empty); + REQUIRE(const_defaulted == const_empty); + // Different sizes + REQUIRE_FALSE(defaulted == buffer); REQUIRE_FALSE(buffer == empty); + REQUIRE_FALSE(const_defaulted == const_buffer); REQUIRE_FALSE(const_buffer == const_empty); // Different values @@ -240,6 +295,7 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { REQUIRE_FALSE(const_buffer == const_other_buffer); // Different const-ness + REQUIRE(defaulted == const_defaulted); REQUIRE(buffer == const_buffer); REQUIRE(empty == const_empty); } @@ -257,6 +313,9 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { } SECTION("value()") { + REQUIRE(defaulted.template value().size() == 0); + REQUIRE(const_defaulted.template value().size() == 0); + auto span = buffer.template value(); REQUIRE(span.size() == 3); REQUIRE(span[0] == one); @@ -286,6 +345,11 @@ TEMPLATE_LIST_TEST_CASE("BufferView", "[wtf]", default_fp_types) { } SECTION("value() const") { + const auto& cdefaulted = defaulted; + const auto& cconst_defaulted = const_defaulted; + REQUIRE(cdefaulted.template value().size() == 0); + REQUIRE(cconst_defaulted.template value().size() == 0); + const auto& cbuffer = buffer; auto span = cbuffer.template value(); REQUIRE(span.size() == 3); diff --git a/tests/unit_tests/wtf/buffer/detail_/contiguous_model.cpp b/tests/unit_tests/wtf/buffer/detail_/contiguous_model.cpp index 9ee08c3..30d7e73 100644 --- a/tests/unit_tests/wtf/buffer/detail_/contiguous_model.cpp +++ b/tests/unit_tests/wtf/buffer/detail_/contiguous_model.cpp @@ -99,6 +99,22 @@ TEMPLATE_LIST_TEST_CASE("ContiguousModel", "[wtf]", all_fp_types) { SECTION("clone_()") { REQUIRE(model.clone()->are_equal(model)); } + SECTION("as_view_()") { + auto pmodel = model.as_view(); + REQUIRE(pmodel->size() == 3); + REQUIRE(pmodel->at(0) == one); + REQUIRE(pmodel->at(1) == two); + REQUIRE(pmodel->at(2) == three); + } + + SECTION("as_view_() const") { + auto pmodel = std::as_const(model).as_view(); + REQUIRE(pmodel->size() == 3); + REQUIRE(pmodel->at(0) == one); + REQUIRE(pmodel->at(1) == two); + REQUIRE(pmodel->at(2) == three); + } + SECTION("at_()") { REQUIRE(model.at(0) == one); REQUIRE(model.at(1) == two); diff --git a/tests/unit_tests/wtf/buffer/float_buffer.cpp b/tests/unit_tests/wtf/buffer/float_buffer.cpp index 2c949b5..d8ca89a 100644 --- a/tests/unit_tests/wtf/buffer/float_buffer.cpp +++ b/tests/unit_tests/wtf/buffer/float_buffer.cpp @@ -16,6 +16,7 @@ #include "../../../test_wtf.hpp" #include +#include using namespace wtf::buffer; using namespace test_wtf; @@ -27,10 +28,14 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { TestType one{1.0}, two{2.0}, three{3.0}; vector_type val{one, two, three}; vector_type empty_vector{}; + + FloatBuffer defaulted; auto buffer = make_float_buffer(val.begin(), val.end()); auto empty = make_float_buffer(empty_vector.begin(), empty_vector.end()); SECTION("ctors and assignment") { + SECTION("default ctor") { REQUIRE(defaulted.size() == 0); } + SECTION("By vector") { FloatBuffer buf_from_vector(val); REQUIRE(buf_from_vector.size() == 3); @@ -48,6 +53,9 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { } SECTION("copy ctor") { + FloatBuffer copy_defaulted(defaulted); + REQUIRE(copy_defaulted.size() == 0); + FloatBuffer copy_buffer(buffer); REQUIRE(copy_buffer == buffer); @@ -88,15 +96,38 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { } } + SECTION("as_view()") { + auto defaulted_view = defaulted.as_view(); + REQUIRE(defaulted_view.size() == 0); + + auto view = buffer.as_view(); + REQUIRE(view.size() == 3); + REQUIRE(view.at(0) == one); + REQUIRE(view.at(1) == two); + REQUIRE(view.at(2) == three); + } + + SECTION("as_view() const") { + auto defaulted_view = std::as_const(defaulted).as_view(); + REQUIRE(defaulted_view.size() == 0); + + auto view = std::as_const(buffer).as_view(); + REQUIRE(view.size() == 3); + REQUIRE(view.at(0) == one); + REQUIRE(view.at(1) == two); + REQUIRE(view.at(2) == three); + } + SECTION("at()") { REQUIRE(buffer.at(0) == one); REQUIRE(buffer.at(1) == two); REQUIRE(buffer.at(2) == three); + REQUIRE_THROWS_AS(defaulted.at(0), std::out_of_range); REQUIRE_THROWS_AS(buffer.at(3), std::out_of_range); REQUIRE_THROWS_AS(empty.at(0), std::out_of_range); - // Can write to it + // Can write to it via a TestType object buffer.at(0) = TestType{4.0}; REQUIRE(buffer.at(0) == TestType{4.0}); } @@ -108,27 +139,35 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { REQUIRE(cholder.at(1) == two); REQUIRE(cholder.at(2) == three); + REQUIRE_THROWS_AS(defaulted.at(0), std::out_of_range); REQUIRE_THROWS_AS(cholder.at(3), std::out_of_range); REQUIRE_THROWS_AS(cempty_holder.at(0), std::out_of_range); } SECTION("size()") { + REQUIRE(defaulted.size() == 0); REQUIRE(buffer.size() == 3); REQUIRE(empty.size() == 0); } SECTION("is_contiguous()") { + REQUIRE(defaulted.is_contiguous()); REQUIRE(buffer.is_contiguous()); REQUIRE(empty.is_contiguous()); } SECTION("operator==") { // Same contents + REQUIRE(defaulted == FloatBuffer{}); REQUIRE(buffer == make_float_buffer(val.begin(), val.end())); REQUIRE(empty == make_float_buffer(empty_vector.begin(), empty_vector.end())); + // Coded to empty is equal to defaulted + REQUIRE(defaulted == empty); + // Different sizes + REQUIRE_FALSE(defaulted == buffer); REQUIRE_FALSE(buffer == empty); // Different values @@ -150,6 +189,8 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { } SECTION("value()") { + REQUIRE(defaulted.template value().size() == 0); + auto span = buffer.template value(); REQUIRE(span.size() == 3); REQUIRE(span[0] == one); @@ -168,6 +209,9 @@ TEMPLATE_LIST_TEST_CASE("FloatBuffer", "[wtf]", default_fp_types) { } SECTION("value() const") { + const auto& cdefaulted = defaulted; + REQUIRE(cdefaulted.template value().size() == 0); + const auto& cbuffer = buffer; auto span = cbuffer.template value(); REQUIRE(span.size() == 3); diff --git a/tests/unit_tests/wtf/fp/float.cpp b/tests/unit_tests/wtf/fp/float.cpp index 9c36f57..eeb2c3b 100644 --- a/tests/unit_tests/wtf/fp/float.cpp +++ b/tests/unit_tests/wtf/fp/float.cpp @@ -88,6 +88,16 @@ TEMPLATE_LIST_TEST_CASE("Float", "[wtf]", test_wtf::all_fp_types) { REQUIRE(f2 == make_float(val)); } + SECTION("as_view()") { + auto view = f.as_view(); + REQUIRE(view == val); + } + + SECTION("as_view_() const") { + auto const_view = std::as_const(f).as_view(); + REQUIRE(const_view == val); + } + SECTION("operator==") { REQUIRE(f == make_float(val)); REQUIRE(f == val); diff --git a/tests/unit_tests/wtf/fp/float_view.cpp b/tests/unit_tests/wtf/fp/float_view.cpp index 6ec3ef6..beecd48 100644 --- a/tests/unit_tests/wtf/fp/float_view.cpp +++ b/tests/unit_tests/wtf/fp/float_view.cpp @@ -15,6 +15,7 @@ */ #include "../../../test_wtf.hpp" +#include #include #include @@ -52,6 +53,12 @@ TEMPLATE_LIST_TEST_CASE("FloatView", "[wtf]", test_wtf::all_fp_types) { REQUIRE(f5 == cf); } + SECTION("From Float object") { + Float f_owning(val); + view_type f2(f_owning); + REQUIRE(f2 == f); + } + SECTION("non-const to const") { const_view_type f2 = f; const_view_type f3(f); @@ -111,7 +118,7 @@ TEMPLATE_LIST_TEST_CASE("FloatView", "[wtf]", test_wtf::all_fp_types) { REQUIRE(pf4 == &f4); } - SECTION("Assign from float") { + SECTION("Assign from float_t") { // Can assign from the same floating point type float_t val2(1.23); f = val2; @@ -123,6 +130,18 @@ TEMPLATE_LIST_TEST_CASE("FloatView", "[wtf]", test_wtf::all_fp_types) { other_t val3(3.14); REQUIRE_THROWS_AS(f = val3, std::runtime_error); } + + SECTION("Assign from Float") { + float_t forty_two{42.0}; + auto f42 = wtf::fp::make_float(forty_two); + auto pf = &(f = f42); + REQUIRE(f == forty_two); + REQUIRE(pf == &f); + + other_t pi{3.14}; + auto fpi = wtf::fp::make_float(pi); + REQUIRE_THROWS_AS(f = fpi, std::invalid_argument); + } } SECTION("swap") { diff --git a/tests/unit_tests/wtf/main.cpp b/tests/unit_tests/wtf/main.cpp index dd429c3..9ce34a9 100644 --- a/tests/unit_tests/wtf/main.cpp +++ b/tests/unit_tests/wtf/main.cpp @@ -17,9 +17,8 @@ #define CATCH_CONFIG_RUNNER #include -int main(int argc, char *argv[]) { +int main(int argc, char* argv[]) { + int res = Catch::Session().run(argc, argv); - int res = Catch::Session().run(argc, argv); - - return res; -} \ No newline at end of file + return res; +}