diff --git a/include/rfl/Generic.hpp b/include/rfl/Generic.hpp index 8169ec64..9d28cdf3 100644 --- a/include/rfl/Generic.hpp +++ b/include/rfl/Generic.hpp @@ -1,9 +1,7 @@ #ifndef RFL_GENERIC_HPP_ #define RFL_GENERIC_HPP_ -#include #include -#include #include #include #include @@ -11,11 +9,24 @@ #include "Object.hpp" #include "Result.hpp" -#include "Variant.hpp" #include "common.hpp" namespace rfl { +/// This is the declaration of the `Generic` class, which serves as a +/// type-agnostic container for various data types. It wraps a `std::variant` +/// that can hold primitive types (bool, int, double, string), null, nested +/// objects, or arrays. This class is typically used in +/// serialization/deserialization libraries to represent JSON or other formats +/// where types are not known at compile time. The class provides constructors +/// to initialize from various types and accessors to retrieve the underlying +/// variant. It mimics the behavior of a `std::optional` or `std::expected` by +/// providing methods like `get()`, `value()`, and `is_null()`. The +/// `VariantType` alias defines the specific set of types this container can +/// hold. The `ReflectionType` alias allows for optional initialization, useful +/// when a value might be absent. Public member functions include constructors, +/// destructor, assignment operators, and accessors. The class is marked with +/// `RFL_API` for DLL export/import support. class RFL_API Generic { public: constexpr static std::nullopt_t Null = std::nullopt; @@ -107,7 +118,8 @@ class RFL_API Generic { Generic& operator=(VariantType&& _value) noexcept; /// Assigns from any type convertible to VariantType. - /// Handles special conversions for numeric types to ensure proper variant alternative selection. + /// Handles special conversions for numeric types to ensure proper variant + /// alternative selection. /// @tparam T The type to convert from /// @param _value The value to assign /// @return Reference to this object @@ -160,69 +172,27 @@ class RFL_API Generic { /// Casts the underlying value to a double or returns an rfl::Error, if the /// underlying value is not a number or the conversion would result in loss of /// precision. - Result to_double() const noexcept { - if (auto* ptr = std::get_if(&value_)) return *ptr; - if (auto* ptr = std::get_if(&value_)) { - auto _d = static_cast(*ptr); - if (static_cast(_d) == *ptr) { - return _d; - } - return error( - "rfl::Generic: Could not cast the underlying value to a double " - "without loss of precision."); - } - return error( - "rfl::Generic: Could not cast the underlying value to a double."); - } + Result to_double() const noexcept; /// Casts the underlying value to an integer or returns an rfl::Error, if the /// underlying value is not an integer. - Result to_int() const noexcept { - if (auto* ptr = std::get_if(&value_)) { - if (*ptr < static_cast(std::numeric_limits::min()) || - *ptr > static_cast(std::numeric_limits::max())) { - return error("rfl::Generic: int64_t value out of range for int."); - } - return static_cast(*ptr); - } - return error( - "rfl::Generic: Could not cast the underlying value to an integer."); - } + Result to_int() const noexcept; /// Casts the underlying value to an int64 or returns an rfl::Error, if the /// underlying value is not an integer. - Result to_int64() const noexcept { - if (auto* ptr = std::get_if(&value_)) return *ptr; - return error( - "rfl::Generic: Could not cast the underlying value to an int64."); - } + Result to_int64() const noexcept; /// Casts the underlying value to an rfl::Generic::Object or returns an /// rfl::Error, if the underlying value is not an rfl::Generic::Object. - Result to_object() const noexcept { - if (auto* ptr = std::get_if(&value_)) return *ptr; - return error( - "rfl::Generic: Could not cast the underlying value to an " - "rfl::Generic::Object."); - } + Result to_object() const noexcept; /// Casts the underlying value to rfl::Generic::Null or returns an /// rfl::Error, if the underlying value is not rfl::Generic::Null. - Result to_null() const noexcept { - if (auto* ptr = std::get_if(&value_)) return *ptr; - return error( - "rfl::Generic: Could not cast the underlying value to " - "rfl::Generic::Null."); - } + Result to_null() const noexcept; /// Casts the underlying value to a string or returns an rfl::Error, if the /// underlying value is not a string. - Result to_string() const noexcept { - if (auto* ptr = std::get_if(&value_)) return *ptr; - return error( - "rfl::Generic: Could not cast the underlying value to a " - "string."); - } + Result to_string() const noexcept; /// Returns the underlying variant. VariantType& variant() noexcept { return value_; }; @@ -236,21 +206,7 @@ class RFL_API Generic { /// @param _rhs The right-hand side Generic /// @return true if both Generics hold equal values friend bool operator==(const Generic& _lhs, const Generic& _rhs) { - if (_lhs.value_.index() != _rhs.value_.index()) { - return false; - } - return std::visit( - [&](const auto& _val) -> bool { - using T = std::remove_cvref_t; - if constexpr (std::is_same_v) { - // Both alternatives are null, which we consider equal. - return true; - } else { - // The indices are equal, so _rhs holds the same alternative. - return _val == std::get(_rhs.value_); - } - }, - _lhs.value_); + return is_same(_lhs, _rhs); } private: @@ -259,6 +215,15 @@ class RFL_API Generic { /// @return The converted variant type static VariantType from_reflection_type(const ReflectionType& _r) noexcept; + /// Checks if two Generic instances are equal by comparing their type indices + /// and their underlying values. This is used to implement the equality + /// operator for the Generic class. + /// @param _lhs The left-hand side Generic instance to compare + /// @param _rhs The right-hand side Generic instance to compare + /// @return true if both instances have the same type and equal values, false + /// otherwise + static bool is_same(const Generic& _lhs, const Generic& _rhs); + private: VariantType value_; }; diff --git a/src/rfl/Generic.cpp b/src/rfl/Generic.cpp index 52ffdce0..bd32d11e 100644 --- a/src/rfl/Generic.cpp +++ b/src/rfl/Generic.cpp @@ -55,6 +55,24 @@ bool Generic::is_null() const noexcept { return std::get_if(&value_) && true; } +bool Generic::is_same(const Generic& _lhs, const Generic& _rhs) { + if (_lhs.value_.index() != _rhs.value_.index()) { + return false; + } + return std::visit( + [&](const auto& _val) -> bool { + using T = std::remove_cvref_t; + if constexpr (std::is_same_v) { + // Both alternatives are null, which we consider equal. + return true; + } else { + // The indices are equal, so _rhs holds the same alternative. + return _val == std::get(_rhs.value_); + } + }, + _lhs.value_); +} + Generic& Generic::operator=(const VariantType& _value) { value_ = _value; return *this; @@ -74,4 +92,60 @@ Generic::ReflectionType Generic::reflection() const noexcept { value_); } +Result Generic::to_double() const noexcept { + if (auto* ptr = std::get_if(&value_)) return *ptr; + if (auto* ptr = std::get_if(&value_)) { + auto _d = static_cast(*ptr); + if (static_cast(_d) == *ptr) { + return _d; + } + return error( + "rfl::Generic: Could not cast the underlying value to a double " + "without loss of precision."); + } + return error( + "rfl::Generic: Could not cast the underlying value to a double."); +} + +Result Generic::to_int() const noexcept { + if (auto* ptr = std::get_if(&value_)) { + if (*ptr < static_cast(std::numeric_limits::min()) || + *ptr > static_cast(std::numeric_limits::max())) { + return error("rfl::Generic: int64_t value out of range for int."); + } + return static_cast(*ptr); + } + return error( + "rfl::Generic: Could not cast the underlying value to an integer."); +} + +Result Generic::to_int64() const noexcept { + if (auto* ptr = std::get_if(&value_)) return *ptr; + return error( + "rfl::Generic: Could not cast the underlying value to an int64."); +} + +Result Generic::to_object() const noexcept { + if (auto* ptr = std::get_if(&value_)) return *ptr; + return error( + "rfl::Generic: Could not cast the underlying value to an " + "rfl::Generic::Object."); +} + +Result Generic::to_null() const noexcept { + if (auto* ptr = std::get_if(&value_)) return *ptr; + return error( + "rfl::Generic: Could not cast the underlying value to " + "rfl::Generic::Null."); +} + +/// Casts the underlying value to a string or returns an rfl::Error, if the +/// underlying value is not a string. +Result Generic::to_string() const noexcept { + if (auto* ptr = std::get_if(&value_)) return *ptr; + return error( + "rfl::Generic: Could not cast the underlying value to a " + "string."); +} + } // namespace rfl