diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index fe6db7e4a..655233b85 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -5,20 +5,20 @@ #include "Engine/Platform/Platform.h" /// -/// Represents a value type that can be assigned null. A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. +/// Wrapper for a value type that can be assigned null, controlling the lifetime of the wrapped value. /// template struct Nullable { private: - union // Prevents default construction of T + union { T _value; }; bool _hasValue; /// - /// Ensures that the lifetime of the wrapped value ends correctly. This method is called when the state of the wrapper is no more needed. + /// Ends the lifetime of the wrapped value by calling its destructor, if the lifetime has not ended yet. Otherwise, does nothing. /// FORCE_INLINE void KillOld() { @@ -30,7 +30,7 @@ private: public: /// - /// Initializes a new instance of the struct with a null value. + /// Initializes by setting the wrapped value to null. /// Nullable() : _hasValue(false) @@ -44,9 +44,9 @@ public: } /// - /// Initializes a new instance of the struct by copying the value. + /// Initializes by copying the wrapped value. /// - /// The initial wrapped value. + /// The initial wrapped value to be copied. Nullable(const T& value) : _value(value) , _hasValue(true) @@ -54,9 +54,9 @@ public: } /// - /// Initializes a new instance of the struct by moving the value. + /// Initializes by moving the wrapped value. /// - /// The initial wrapped value. + /// The initial wrapped value to be moved. Nullable(T&& value) noexcept : _value(MoveTemp(value)) , _hasValue(true) @@ -64,7 +64,7 @@ public: } /// - /// Initializes a new instance of the struct by copying the value from another instance. + /// Initializes by copying another . /// /// The wrapped value to be copied. Nullable(const Nullable& other) @@ -74,7 +74,7 @@ public: } /// - /// Initializes a new instance of the struct by moving the value from another instance. + /// Initializes by moving another . /// /// The wrapped value to be moved. Nullable(Nullable&& other) noexcept @@ -89,6 +89,9 @@ public: other.Reset(); } + /// + /// Reassigns the wrapped value by copying. + /// auto operator=(const T& value) -> Nullable& { KillOld(); @@ -99,6 +102,9 @@ public: return *this; } + /// + /// Reassigns the wrapped value by moving. + /// auto operator=(T&& value) noexcept -> Nullable& { KillOld(); @@ -109,6 +115,9 @@ public: return *this; } + /// + /// Reassigns the wrapped value by copying another . + /// auto operator=(const Nullable& other) -> Nullable& { KillOld(); @@ -122,6 +131,9 @@ public: return *this; } + /// + /// Reassigns the wrapped value by moving another . + /// auto operator=(Nullable&& other) noexcept -> Nullable& { if (this == &other) @@ -143,7 +155,7 @@ public: } /// - /// Gets a value indicating whether the current NullableBase{T} object has a valid value of its underlying type. + /// Checks if wrapped object has a valid value. /// /// true if this object has a valid value; otherwise, false. FORCE_INLINE bool HasValue() const @@ -152,9 +164,9 @@ public: } /// - /// Gets the value of the current NullableBase{T} object if it has been assigned a valid underlying value. + /// Gets a const reference to the wrapped value. If the value is not valid, the behavior is undefined. /// - /// The value. + /// Reference to the wrapped value. FORCE_INLINE const T& GetValue() const { ASSERT(_hasValue); @@ -162,29 +174,36 @@ public: } /// - /// Gets a reference to the value of the current NullableBase{T} object. - /// If is assumed that the value is valid, otherwise the behavior is undefined. + /// Gets a reference to the wrapped value. If the value is not valid, the behavior is undefined. + /// This method can be used to reassign the wrapped value. /// - /// In the past, this value returned a copy of the stored value. Be careful. - /// Reference to the value. + /// Reference to the wrapped value. FORCE_INLINE T& GetValue() { ASSERT(_hasValue); return _value; } + /// + /// Gets a const reference to the wrapped value or a default value if the value is not valid. + /// + /// Reference to the wrapped value or the default value. FORCE_INLINE const T& GetValueOr(const T& defaultValue) const { return _hasValue ? _value : defaultValue; } + /// + /// Gets an instance of the wrapped value or a default value based on r-value reference, if the wrapped value is not valid. + /// + /// Copy of the wrapped value or the default value. FORCE_INLINE T GetValueOr(T&& defaultValue) const noexcept { return _hasValue ? _value : defaultValue; } /// - /// Sets the wrapped value. + /// Sets the wrapped value by copying. /// /// The value to be copied. FORCE_INLINE void SetValue(const T& value) @@ -199,7 +218,7 @@ public: } /// - /// Sets the wrapped value. + /// Sets the wrapped value by moving. /// /// The value to be moved. FORCE_INLINE void SetValue(T&& value) noexcept @@ -210,18 +229,10 @@ public: _hasValue = true; // Set the flag AFTER the value is moved. } - FORCE_INLINE bool TrySet(T&& value) noexcept - { - if (_hasValue) - { - return false; - } - - new (&_value) T(MoveTemp(value)); // Placement new (move constructor) - _hasValue = true; // Set the flag AFTER the value is moved. - return true; - } - + /// + /// If the wrapped value is not valid, sets it by copying. Otherwise, does nothing. + /// + /// True if the wrapped value was changed, otherwise false. FORCE_INLINE bool TrySet(const T& value) { if (_hasValue) @@ -235,7 +246,23 @@ public: } /// - /// Resets the value. + /// If the wrapped value is not valid, sets it by moving. Otherwise, does nothing. + /// + /// True if the wrapped value was changed, otherwise false. + FORCE_INLINE bool TrySet(T&& value) noexcept + { + if (_hasValue) + { + return false; + } + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; // Set the flag AFTER the value is moved. + return true; + } + + /// + /// Disposes the wrapped value and sets the wrapped value to null. If the wrapped value is not valid, does nothing. /// FORCE_INLINE void Reset() { @@ -244,8 +271,9 @@ public: } /// - /// Moves the value from the current NullableBase{T} object and resets it. + /// Moves the wrapped value to the output parameter and sets the wrapped value to null. If the wrapped value is not valid, the behavior is undefined. /// + /// The output parameter that will receive the wrapped value. FORCE_INLINE void GetAndReset(T& value) { ASSERT(_hasValue); @@ -254,10 +282,10 @@ public: } /// - /// Indicates whether the current NullableBase{T} object is equal to a specified object. + /// Indicates whether this instance is equal to other one. /// /// The other object. - /// True if both values are equal. + /// true if both values are equal. FORCE_INLINE bool operator==(const Nullable& other) const { if (other._hasValue != _hasValue) @@ -269,20 +297,19 @@ public: } /// - /// Indicates whether the current NullableBase{T} object is not equal to a specified object. + /// Indicates whether this instance is NOT equal to other one. /// /// The other object. - /// True if both values are not equal. + /// true if both values are not equal. FORCE_INLINE bool operator!=(const Nullable& other) const { return !operator==(other); } /// - /// Explicit conversion to boolean value. + /// Explicit conversion to boolean value. Allows to check if the wrapped value is valid in if-statements without casting. /// - /// True if this object has a valid value, otherwise false - /// Hint: If-statements are able to use explicit cast implicitly (sic). + /// true if this object has a valid value, otherwise false FORCE_INLINE explicit operator bool() const { return _hasValue; @@ -290,69 +317,107 @@ public: }; /// -/// Nullable value container that contains a boolean value or null. +/// Specialization of for type. /// template<> struct Nullable { private: + /// + /// Underlying value of the nullable boolean. Uses only one byte to optimize memory usage. + /// enum class Value : uint8 { - False = 0, - True = 1, - Null = 2, + Null, + False, + True, }; Value _value = Value::Null; public: + /// + /// Initializes nullable boolean by setting the wrapped value to null. + /// Nullable() = default; ~Nullable() = default; - + /// + /// Initializes nullable boolean by moving another nullable boolean. + /// Nullable(Nullable&& value) = default; + /// + /// Initializes nullable boolean by copying another nullable boolean. + /// Nullable(const Nullable& value) = default; + /// + /// Initializes nullable boolean by implicitly casting a boolean value. + /// Nullable(const bool value) noexcept { _value = value ? Value::True : Value::False; } + /// + /// Reassigns the wrapped value by implicitly casting a boolean value. + /// auto operator=(const bool value) noexcept -> Nullable& { _value = value ? Value::True : Value::False; return *this; } + /// + /// Reassigns the wrapped value by copying another nullable boolean. + /// auto operator=(const Nullable& value) -> Nullable& = default; + /// + /// Reassigns the wrapped value by moving another nullable boolean. + /// auto operator=(Nullable&& value) -> Nullable& = default; + /// + /// Checks if wrapped bool has a valid value. + /// FORCE_INLINE bool HasValue() const noexcept { return _value != Value::Null; } + /// + /// Gets the wrapped boolean value. If the value is not valid, the behavior is undefined. + /// FORCE_INLINE bool GetValue() const { ASSERT(_value != Value::Null); return _value == Value::True; } + /// + /// Gets the wrapped boolean value. If the value is not valid, returns the default value. + /// FORCE_INLINE bool GetValueOr(const bool defaultValue) const noexcept { return _value == Value::Null ? defaultValue : _value == Value::True; } + /// + /// Sets the wrapped value to a valid boolean. + /// FORCE_INLINE void SetValue(const bool value) noexcept { _value = value ? Value::True : Value::False; } + /// + /// If the wrapped value is not valid, sets it to a valid boolean. + /// FORCE_INLINE bool TrySet(const bool value) noexcept { if (_value != Value::Null) @@ -364,11 +429,17 @@ public: return true; } + /// + /// Sets the wrapped bool to null. + /// FORCE_INLINE void Reset() noexcept { _value = Value::Null; } + /// + /// Moves the wrapped value to the output parameter and sets the wrapped value to null. If the wrapped value is not valid, the behavior is undefined. + /// FORCE_INLINE void GetAndReset(bool& value) noexcept { ASSERT(_value != Value::Null); @@ -378,27 +449,26 @@ public: /// - /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to true. + /// Checks if the current object has a valid value and it's set to true. If the value is false or not valid, the method returns false. /// - /// true if this object has a valid value set to true; otherwise, false. FORCE_INLINE bool IsTrue() const { return _value == Value::True; } /// - /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to false. + /// Checks if the current object has a valid value and it's set to false. If the value is true or not valid, the method returns false. /// - /// true if this object has a valid value set to false; otherwise, false. FORCE_INLINE bool IsFalse() const { return _value == Value::False; } /// - /// Getting if provoke unacceptably ambiguous code. For template meta-programming use explicit HasValue() instead. + /// Deletes implicit conversion to bool to prevent ambiguous code. /// + /// + /// Implicit cast from nullable bool to a bool produces unacceptably ambiguous code. For template meta-programming use explicit HasValue instead. + /// explicit operator bool() const = delete; - - // Note: Even though IsTrue and IsFalse have been added for convenience, but they may be used for performance reasons. };