Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/wtf/concepts/concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <wtf/concepts/floating_point.hpp>
#include <wtf/concepts/iterator.hpp>
#include <wtf/concepts/modifiers.hpp>
#include <wtf/concepts/stream_insertion.hpp>

/// Contains concepts used throughout the WTF library
namespace wtf::concepts {}
35 changes: 35 additions & 0 deletions include/wtf/concepts/stream_insertion.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2026 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

namespace wtf::concepts {

/** @brief Determines if the type @p T can be inserted into an ostream.
*
* @tparam T Type to check for stream insertion support.
*
* The StreamInsertable concept is satisfied if an object of type @p T can be
* inserted into an std::ostream using the << operator. Falling usual C++
* conventions, this usually means that an object of type @p T can be
* printed.
*/
template<typename T>
concept StreamInsertable = requires(std::ostream& os, const T& obj) {
{ os << obj } -> std::same_as<std::ostream&>;
};

} // namespace wtf::concepts
18 changes: 18 additions & 0 deletions include/wtf/fp/detail_/float_holder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class FloatHolder {
/// Type of a pointer to a const_float_view_type object
using const_float_view_pointer = std::unique_ptr<const_float_view_type>;

/// Type of a string representing the held floating-point value
using string_type = std::string;

/// Default virtual destructor
virtual ~FloatHolder() = default;

Expand Down Expand Up @@ -170,6 +173,18 @@ class FloatHolder {
*/
bool is_const() const { return is_const_(); }

/** @brief Returns a string-representation of the held value.
*
* This method is used to get a string-representation of the floating-point
* object held by *this. This method is implemented by calling
* to_string_().
*
* @return A string representing the held floating-point value.
*
* @throw ??? Throws if converting the type-erased value to a string
*/
string_type to_string() const { return to_string_(); }

protected:
/// Initializes *this with the given type info object
explicit FloatHolder(type_info ti) : m_type_(std::move(ti)) {}
Expand All @@ -193,6 +208,9 @@ class FloatHolder {
/// Base checks that types are equal, derived need only check values
virtual bool are_equal_(const FloatHolder& other) const = 0;

/// Used to implement to_string_
virtual string_type to_string_() const = 0;

/// The RTTI object for the object held by the derived class
type_info m_type_;
};
Expand Down
13 changes: 13 additions & 0 deletions include/wtf/fp/detail_/float_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/

#pragma once
#include <sstream>
#include <utility> // for std::move, std::swap
#include <wtf/concepts/floating_point.hpp>
#include <wtf/concepts/stream_insertion.hpp>
#include <wtf/fp/detail_/float_holder.hpp>
#include <wtf/fp/detail_/float_view_model.hpp>
#include <wtf/type_traits/float_traits.hpp>
Expand Down Expand Up @@ -203,6 +205,17 @@ class FloatModel : public FloatHolder {
return false;
}

/// Implements FloatHolder::to_string by using stringstream
string_type to_string_() const override {
if constexpr(concepts::StreamInsertable<FloatType>) {
std::stringstream ss;
ss << value_;
return ss.str();
} else {
return "<unprintable float>";
}
}

/// The value being held
value_type value_;
};
Expand Down
18 changes: 18 additions & 0 deletions include/wtf/fp/detail_/float_view_holder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class FloatViewHolder {
/// Read-only reference to type_info object
using const_type_info_reference = const type_info&;

/// Type of a string representation of *this
using string_type = std::string;

/// Default virtual destructor
virtual ~FloatViewHolder() = default;

Expand Down Expand Up @@ -177,6 +180,18 @@ class FloatViewHolder {
}
}

/** @brief Returns a string-representation of the held value.
*
* This method is used to get a string-representation of the floating-point
* object held by *this. This method is implemented by calling
* to_string_().
*
* @return A string representing the held floating-point value.
*
* @throw ??? Throws if converting the type-erased value to a string
*/
string_type to_string() const { return to_string_(); }

protected:
/// Initializes *this with the given type info object
explicit FloatViewHolder(type_info ti) : m_type_(std::move(ti)) {}
Expand All @@ -194,6 +209,9 @@ class FloatViewHolder {
/// Base checks that types are equal, derived need only check values
virtual bool are_equal_(const_holder_reference other) const = 0;

/// Derived class should override to provide string representation
virtual string_type to_string_() const = 0;

/// The RTTI object for the object held by the derived class
type_info m_type_;
};
Expand Down
15 changes: 14 additions & 1 deletion include/wtf/fp/detail_/float_view_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#pragma once
#include <utility> // for std::move, std::swap
#include <wtf/concepts/floating_point.hpp>
#include <wtf/concepts/stream_insertion.hpp>
#include <wtf/forward.hpp>
#include <wtf/fp/detail_/float_view_holder.hpp>
#include <wtf/type_traits/float_traits.hpp>
Expand Down Expand Up @@ -58,6 +59,7 @@ class FloatViewModel
///@{
using const_holder_type = typename holder_type::const_holder_type;
using typename holder_type::const_holder_reference;
using typename holder_type::string_type;
///@}

/// Type defining the traits for FloatType
Expand Down Expand Up @@ -214,14 +216,25 @@ class FloatViewModel
}
}

/// Implements FloatHolder::are_equal by downcasting and calling operator==
/// Implements are_equal by downcasting and calling operator==
bool are_equal_(const_holder_reference other) const override {
if(auto* p = dynamic_cast<const FloatViewModel*>(&other)) {
return *this == *p;
}
return false;
}

/// Implements FloatViewHolder::to_string by using stringstream
string_type to_string_() const override {
if constexpr(concepts::StreamInsertable<FloatType>) {
std::stringstream ss;
ss << *m_pvalue_;
return ss.str();
} else {
return "<unprintable float>";
}
}

/// The value being aliased
pointer m_pvalue_;
};
Expand Down
19 changes: 19 additions & 0 deletions include/wtf/fp/float.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class Float {
/// Type of a read-only view acting like *this
using const_view_type = FloatView<const Float>;

/// Type of converting *this to a string
using string_type = holder_type::string_type;

// -------------------------------------------------------------------------
// Ctors and assignment operators
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -201,7 +204,23 @@ class Float {
*/
bool operator!=(const Float& other) const { return !(*this == other); }

/** @brief Provides a string representation of the held value.
*
* If *this is not holding a value, an empty string is returned.
*
* @return A string representation of the held value.
*
* @throw ??? Throws if converting the held value to a string throws.
* Strong throw guarantee.
*/
string_type to_string() const {
return is_holding_() ? m_holder_->to_string() : string_type{};
}

private:
/// Determines if *this is holding a value or not
bool is_holding_() const noexcept { return m_holder_ != nullptr; }

template<typename TupleType, typename Visitor, typename... Args>
friend auto visit_float(Visitor&& visitor, Args&&... args);

Expand Down
17 changes: 17 additions & 0 deletions include/wtf/fp/float_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class FloatView {
/// Add types from holder_type to API
///@{
using holder_pointer = typename holder_type::holder_pointer;
using string_type = typename holder_type::string_type;
///@}

/** @brief Creates a FloatView that aliases @p value.
Expand Down Expand Up @@ -353,13 +354,29 @@ class FloatView {
(concepts::ConstQualified<T> || concepts::Unmodified<T>))
T value() const;

/** @brief Provides a string representation of the held value.
*
* If *this is not holding a value, an empty string is returned.
*
* @return A string representation of the held value.
*
* @throw ??? Throws if converting the held value to a string throws.
* Strong throw guarantee.
*/
string_type to_string() const {
return is_holding_() ? m_pfloat_->to_string() : string_type{};
}

private:
template<concepts::WTFFloat FloatType2>
friend class FloatView;

template<concepts::FloatingPoint T>
friend auto make_float_view(T& value);

/// Determines if *this is holding a value or not
bool is_holding_() const noexcept { return m_pfloat_ != nullptr; }

/// The holder object
holder_pointer m_pfloat_;
};
Expand Down
39 changes: 39 additions & 0 deletions tests/unit_tests/wtf/concepts/stream_insertion.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2026 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.
*/

#include "../../../test_wtf.hpp"
#include <wtf/concepts/stream_insertion.hpp>

namespace {

struct Insertable {};

[[maybe_unused]] std::ostream& operator<<(std::ostream& os, const Insertable&) {
return os;
}

struct NotInsertable {};

} // namespace

TEST_CASE("StreamInsertable concept", "[wtf][concepts]") {
using namespace wtf::concepts;

STATIC_REQUIRE(StreamInsertable<Insertable>);
STATIC_REQUIRE_FALSE(StreamInsertable<NotInsertable>);
STATIC_REQUIRE(StreamInsertable<float>);
STATIC_REQUIRE(StreamInsertable<double>);
}
10 changes: 10 additions & 0 deletions tests/unit_tests/wtf/fp/detail_/float_holder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,14 @@ TEMPLATE_LIST_TEST_CASE("FloatHolder", "[wtf]", all_fp_types) {
holder_t& h4 = m4;
REQUIRE_FALSE(h.are_equal(h4));
}

SECTION("to_string") {
if constexpr(wtf::concepts::StreamInsertable<float_t>) {
std::stringstream ss;
ss << val;
REQUIRE(h.to_string() == ss.str());
} else {
REQUIRE(h.to_string() == "<unprintable float>");
}
}
}
10 changes: 10 additions & 0 deletions tests/unit_tests/wtf/fp/detail_/float_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ TEMPLATE_LIST_TEST_CASE("FloatModel", "[float_model]", test_wtf::all_fp_types) {
FloatModel<other_type> m4(other_type(3.14));
REQUIRE_FALSE(m.are_equal(m4));
}

SECTION("to_string_") {
if constexpr(wtf::concepts::StreamInsertable<float_t>) {
std::stringstream ss;
ss << val;
REQUIRE(m.to_string() == ss.str());
} else {
REQUIRE(m.to_string() == "<unprintable float>");
}
}
}

TEMPLATE_LIST_TEST_CASE("visit_float_model", "[float_model]",
Expand Down
12 changes: 12 additions & 0 deletions tests/unit_tests/wtf/fp/detail_/float_view_holder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,16 @@ TEMPLATE_LIST_TEST_CASE("FloatViewHolder", "[wtf]", all_fp_types) {
// Different const-ness
REQUIRE_FALSE(h.are_equal(ch));
}

SECTION("to_string") {
if constexpr(wtf::concepts::StreamInsertable<float_t>) {
std::stringstream ss;
ss << val;
REQUIRE(h.to_string() == ss.str());
REQUIRE(ch.to_string() == ss.str());
} else {
REQUIRE(h.to_string() == "<unprintable float>");
REQUIRE(ch.to_string() == "<unprintable float>");
}
}
}
12 changes: 12 additions & 0 deletions tests/unit_tests/wtf/fp/detail_/float_view_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,16 @@ TEMPLATE_LIST_TEST_CASE("FloatViewModel", "[float_model]",
// Different const-ness
REQUIRE_FALSE(m.are_equal(cm));
}

SECTION("to_string_") {
if constexpr(wtf::concepts::StreamInsertable<float_t>) {
std::stringstream ss;
ss << val;
REQUIRE(m.to_string() == ss.str());
REQUIRE(cm.to_string() == ss.str());
} else {
REQUIRE(m.to_string() == "<unprintable float>");
REQUIRE(cm.to_string() == "<unprintable float>");
}
}
}
10 changes: 10 additions & 0 deletions tests/unit_tests/wtf/fp/float.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ TEMPLATE_LIST_TEST_CASE("Float", "[wtf]", test_wtf::all_fp_types) {
Float f2(other_val);
REQUIRE(f != f2);
}

SECTION("to_string") {
if constexpr(wtf::concepts::StreamInsertable<float_t>) {
std::stringstream ss;
ss << val;
REQUIRE(f.to_string() == ss.str());
} else {
REQUIRE(f.to_string() == "<unprintable float>");
}
}
}

TEST_CASE("make_float", "[wtf]") {
Expand Down
Loading