diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 9511f0df2..f291eb0dc 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -8,39 +8,132 @@ /// 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. /// template -struct NullableBase +struct Nullable { -protected: +private: + union // Prevents default construction of T + { + T _value; + }; bool _hasValue; - T _value; + + /// + /// Ensures that the lifetime of the wrapped value ends correctly. This method is called when the state of the wrapper is no more needed. + /// + FORCE_INLINE void KillOld() + { + if (_hasValue) + { + _value.~T(); + } + } public: /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct with a null value. /// - NullableBase() + Nullable() + : _hasValue(false) { - _hasValue = false; + // Value is not initialized. + } + + ~Nullable() + { + KillOld(); } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct by copying the value. /// - /// The initial value. - NullableBase(const T& value) + /// The initial wrapped value. + Nullable(const T& value) + : _value(value) + , _hasValue(true) { - _value = value; - _hasValue = true; } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct by moving the value. /// - /// The other. - NullableBase(const NullableBase& other) + /// The initial wrapped value. + Nullable(T&& value) noexcept + : _value(MoveTemp(value)) + , _hasValue(true) + { + } + + /// + /// Initializes a new instance of the struct by copying the value from another instance. + /// + /// The wrapped value to be copied. + Nullable(const Nullable& other) + : _value(other._value) + , _hasValue(other._hasValue) + { + } + + /// + /// Initializes a new instance of the struct by moving the value from another instance. + /// + /// The wrapped value to be moved. + Nullable(Nullable&& other) noexcept { - _value = other._value; _hasValue = other._hasValue; + if (_hasValue) + { + new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) + } + + other.Reset(); + } + + auto operator=(const T& value) -> Nullable& + { + KillOld(); + + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; + + return *this; + } + + auto operator=(T&& value) noexcept -> Nullable& + { + KillOld(); + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; + + return *this; + } + + auto operator=(const Nullable& other) -> Nullable& + { + KillOld(); + + _hasValue = other._hasValue; + if (_hasValue) + { + new (&_value) T(other._value); // Placement new (copy constructor) + } + + return *this; + } + + auto operator=(Nullable&& other) noexcept -> Nullable& + { + KillOld(); + + _hasValue = other._hasValue; + if (_hasValue) + { + new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) + + other.Reset(); + } + + return *this; } public: @@ -64,30 +157,61 @@ public: } /// - /// Gets the value of the current NullableBase{T} object if it has been assigned a valid underlying value. + /// 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. /// - /// The value. - FORCE_INLINE T GetValue() + /// In the past, this value returned a copy of the stored value. Be careful. + /// Reference to the value. + FORCE_INLINE T& GetValue() { ASSERT(_hasValue); return _value; } /// - /// Sets the value. + /// Sets the wrapped value. /// - /// The value. - void SetValue(const T& value) + /// The value to be copied. + FORCE_INLINE void SetValue(const T& value) { - _value = value; + if (_hasValue) + { + _value.~T(); + } + + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; + } + + /// + /// Sets the wrapped value. + /// + /// The value to be moved. + FORCE_INLINE void SetValue(T&& value) noexcept + { + KillOld(); + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) _hasValue = true; } /// /// Resets the value. /// - void Reset() + FORCE_INLINE void Reset() { + KillOld(); + _hasValue = false; + } + + /// + /// Moves the value from the current NullableBase{T} object and resets it. + /// + FORCE_INLINE void GetAndReset(T& value) + { + ASSERT(_hasValue); + value = MoveTemp(_value); + _value.~T(); // Move is not destructive. _hasValue = false; } @@ -97,14 +221,14 @@ public: /// /// The other object. /// True if both values are equal. - bool operator==(const NullableBase& other) const + FORCE_INLINE bool operator==(const Nullable& other) const { - if (_hasValue) + if (other._hasValue != _hasValue) { - return other._hasValue && _value == other._value; + return false; } - return !other._hasValue; + return _value == other._value; } /// @@ -112,43 +236,19 @@ public: /// /// The other object. /// True if both values are not equal. - FORCE_INLINE bool operator!=(const NullableBase& other) const + FORCE_INLINE bool operator!=(const Nullable& other) const { return !operator==(other); } -}; - -/// -/// 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. -/// -template -struct Nullable : NullableBase -{ -public: - /// - /// Initializes a new instance of the struct. - /// - Nullable() - : NullableBase() - { - } /// - /// Initializes a new instance of the struct. + /// Explicit conversion to boolean value. /// - /// The initial value. - Nullable(const T& value) - : NullableBase(value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The other. - Nullable(const Nullable& other) - : NullableBase(other) + /// True if this object has a valid value, otherwise false + /// Hint: If-statements are able to use explicit cast implicitly (sic). + FORCE_INLINE explicit operator bool() const { + return _hasValue; } }; @@ -156,43 +256,81 @@ public: /// Nullable value container that contains a boolean value or null. /// template<> -struct Nullable : NullableBase +struct Nullable { -public: - /// - /// Initializes a new instance of the struct. - /// - Nullable() - : NullableBase() +private: + enum class Value : uint8 { - } + False = 0, + True = 1, + Null = 2, + }; - /// - /// Initializes a new instance of the struct. - /// - /// The initial value. - Nullable(bool value) - : NullableBase(value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The other. - Nullable(const Nullable& other) - : NullableBase(other) - { - } + Value _value = Value::Null; public: + Nullable() = default; + + ~Nullable() = default; + + + Nullable(Nullable&& value) = default; + + Nullable(const Nullable& value) = default; + + Nullable(const bool value) noexcept + { + _value = value ? Value::True : Value::False; + } + + + auto operator=(const bool value) noexcept -> Nullable& + { + _value = value ? Value::True : Value::False; + return *this; + } + + auto operator=(const Nullable& value) -> Nullable& = default; + + auto operator=(Nullable&& value) -> Nullable& = default; + + + FORCE_INLINE bool HasValue() const noexcept + { + return _value != Value::Null; + } + + FORCE_INLINE bool GetValue() const + { + ASSERT(_value != Value::Null); + return _value == Value::True; + } + + FORCE_INLINE void SetValue(const bool value) noexcept + { + _value = value ? Value::True : Value::False; + } + + FORCE_INLINE void Reset() noexcept + { + _value = Value::Null; + } + + FORCE_INLINE void GetAndReset(bool& value) noexcept + { + ASSERT(_value != Value::Null); + value = _value == Value::True; + _value = Value::Null; + } + + /// /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to true. /// /// true if this object has a valid value set to true; otherwise, false. FORCE_INLINE bool IsTrue() const { - return _hasValue && _value; + return _value == Value::True; } /// @@ -201,15 +339,13 @@ public: /// true if this object has a valid value set to false; otherwise, false. FORCE_INLINE bool IsFalse() const { - return _hasValue && !_value; + return _value == Value::False; } /// - /// Implicit conversion to boolean value. + /// Getting if provoke unacceptably ambiguous code. For template meta-programming use explicit HasValue() instead. /// - /// True if this object has a valid value set to true, otherwise false - FORCE_INLINE operator bool() const - { - return _hasValue && _value; - } + explicit operator bool() const = delete; + + // Note: Even though IsTrue and IsFalse have been added for convenience, but they may be used for performance reasons. };