// Copyright 2018 Google LLC // // 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 // // https://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. #ifndef ASTC_CODEC_BASE_OPTIONAL_H_ #define ASTC_CODEC_BASE_OPTIONAL_H_ #include "src/base/type_traits.h" #include #include #include #include #include // Optional - a template class to store an optional value of type T. // // Usage examples: // // Initialization and construction: // Optional foo; // |foo| doesn't contain a value. // Optional foo(Foo(10)); // |foo| contains a copy-constructed value. // Optional foo2(foo); // |foo2| contains a copy of |foo|'s value. // Optional foo3(std::move(foo2)); // Guess what? // // Assignment: // Foo foo_value(0); // Optional foo; // |foo| is empty. // Optional foo2; // |foo2| is empty. // foo2 = foo; // |foo2| is still empty. // foo = foo_value; // set value of |foo| to a copy of |foo_value| // foo = std::move(foo_value); // move |foo_value| into |foo|. // foo2 = foo; // now |foo2| has a copy of |foo|'s value. // foo = kNullopt; // unset |foo|, it has no value. // // Checking and accessing value: // if (foo) { // // |foo| has a value. // doStuff(*foo); // |*foo| is the value inside |foo|. // foo->callMethod(); // Same as (*foo).callMethod(). // } else { // // |foo| is empty. // } // // foo.value() // Same as *foo // foo.valueOr() // Return is |foo| has no value. // // In-place construction: // // Optional foo; // |foo| is empty. // foo.emplace(20); // |foo| now contains a value constructed as Foo(20) // // Optional foo(kInplace, 20); // |foo| is initialized with a value // // that is constructed in-place as // // Foo(20). // // return makeOptional(20); // Takes Foo constructor arguments // // directly. // // Returning values: // // Optional myFunc(...) { // if (someCondition) { // return Foo(10); // call Optional(Foo&) constructor. // } else { // return {}; // call Optional() constructor, which // // builds an empty value. // } // } // // Memory layout: // Optional is equivalent to: // // struct { // bool flag; // Foo value; // }; // // in terms of memory layout. This means it *doubles* the size of integral // types. Also: // // - Optional can be constructed from anything that constructs a Foo. // // - Same with Optional(kInplace, Args...) where Args... matches any // arguments that can be passed to a Foo constructor. // // - Comparison operators are provided. Beware: an empty Optional // is always smaller than any Foo value. namespace astc_codec { namespace base { namespace details { // Base classes to reduce the number of instantiations of the Optional's // internal members. class OptionalFlagBase { public: void setConstructed(bool constructed) { mConstructed = constructed; } constexpr bool constructed() const { return mConstructed; } constexpr operator bool() const { return constructed(); } bool hasValue() const { return constructed(); } constexpr OptionalFlagBase(bool constructed = false) : mConstructed(constructed) { } private: bool mConstructed = false; }; template class OptionalStorageBase { protected: using StoreT = typename std::aligned_storage::type; StoreT mStorage = {}; }; } // namespace details // A tag type for empty optional construction struct NulloptT { constexpr explicit NulloptT(int) { } }; // A tag type for inplace value construction struct InplaceT { constexpr explicit InplaceT(int) { } }; // Tag values for null optional and inplace construction constexpr NulloptT kNullopt{1}; constexpr InplaceT kInplace{1}; // Forward declaration for an early use template class Optional; // A type trait for checking if a type is an optional instantiation // Note: if you want to refer to the template name inside the template, // you need to declare this alias outside of it - because the // class name inside of the template stands for an instantiated template // E.g, for template class Foo if you say 'Foo' inside the class, it // actually means Foo; template using is_any_optional = is_template_instantiation_of::type, Optional>; template class Optional : private details::OptionalFlagBase, private details::OptionalStorageBase::value> { // make sure all optionals are buddies - this is needed to implement // conversion from optionals of other types template friend class Optional; template using self = Optional; using base_flag = details::OptionalFlagBase; using base_storage = details::OptionalStorageBase::value>; public: // std::optional will have this, so let's provide it using value_type = T; // make sure we forbid some Optional instantiations where things may get // really messy static_assert(!std::is_same::type, NulloptT>::value, "Optional of NulloptT is not allowed"); static_assert(!std::is_same::type, InplaceT>::value, "Optional of InplaceT is not allowed"); static_assert(!std::is_reference::value, "Optional references are not allowed: use a pointer instead"); // constructors constexpr Optional() { } constexpr Optional(NulloptT) { } Optional(const Optional& other) : base_flag(other.constructed()) { if (this->constructed()) { new (&get()) T(other.get()); } } Optional(Optional&& other) : base_flag(other.constructed()) { if (this->constructed()) { new (&get()) T(std::move(other.get())); } } // Conversion constructor from optional of similar type template::value && std::is_constructible::value>> Optional(const Optional& other) : base_flag(other.constructed()) { if (this->constructed()) { new (&get()) T(other.get()); } } // Move-conversion constructor template::value && std::is_constructible::value>> Optional(Optional&& other) : base_flag(other.constructed()) { if (this->constructed()) { new (&get()) T(std::move(other.get())); } } // Construction from a raw value Optional(const T& value) : base_flag(true) { new (&get()) T(value); } // Move construction from a raw value Optional(T&& value) : base_flag(true) { new (&get()) T(std::move(value)); } // Inplace construction from a list of |T|'s ctor arguments template Optional(InplaceT, Args&&... args) : base_flag(true) { new (&get()) T(std::forward(args)...); } // Inplace construction from an initializer list passed into |T|'s ctor template>>> Optional(InplaceT, std::initializer_list il) : base_flag(true) { new (&get()) T(il); } // direct assignment Optional& operator=(const Optional& other) { if (&other == this) { return *this; } if (this->constructed()) { if (other.constructed()) { get() = other.get(); } else { destruct(); this->setConstructed(false); } } else { if (other.constructed()) { new (&get()) T(other.get()); this->setConstructed(true); } else { ; // we're good } } return *this; } // move assignment Optional& operator=(Optional&& other) { if (this->constructed()) { if (other.constructed()) { get() = std::move(other.get()); } else { destruct(); this->setConstructed(false); } } else { if (other.constructed()) { new (&get()) T(std::move(other.get())); this->setConstructed(true); } else { ; // we're good } } return *this; } // conversion assignment template::type, T>> Optional& operator=(const Optional& other) { if (this->constructed()) { if (other.constructed()) { get() = other.get(); } else { destruct(); this->setConstructed(false); } } else { if (other.constructed()) { new (&get()) T(other.get()); this->setConstructed(true); } else { ; // we're good } } return *this; } // conversion move assignment template::type, T>> Optional& operator=(Optional&& other) { if (this->constructed()) { if (other.constructed()) { get() = std::move(other.get()); } else { destruct(); this->setConstructed(false); } } else { if (other.constructed()) { new (&get()) T(std::move(other.get())); this->setConstructed(true); } else { ; // we're good } } return *this; } // the most complicated one: forwarding constructor for anything convertible // to |T|, excluding the stuff implemented above explicitly template::type>::value && std::is_convertible::type, T>::value>> Optional& operator=(U&& other) { if (this->constructed()) { get() = std::forward(other); } else { new (&get()) T(std::forward(other)); this->setConstructed(true); } return *this; } // Adopt value checkers from the parent using base_flag::operator bool; using base_flag::hasValue; T& value() { assert(this->constructed()); return get(); } constexpr const T& value() const { assert(this->constructed()); return get(); } T* ptr() { return this->constructed() ? &get() : nullptr; } constexpr const T* ptr() const { return this->constructed() ? &get() : nullptr; } // Value getter with fallback template::type, T>> constexpr T valueOr(U&& defaultValue) const { return this->constructed() ? get() : std::move(defaultValue); } // Pointer-like operators T& operator*() { assert(this->constructed()); return get(); } constexpr const T& operator*() const { assert(this->constructed()); return get(); } T* operator->() { assert(this->constructed()); return &get(); } constexpr const T* operator->() const { assert(this->constructed()); return &get(); } ~Optional() { if (this->constructed()) { destruct(); } } void clear() { if (this->constructed()) { destruct(); this->setConstructed(false); } } template::type, T>> void reset(U&& u) { *this = std::forward(u); } // In-place construction with possible destruction of the old value template void emplace(Args&&... args) { if (this->constructed()) { destruct(); } new (&get()) T(std::forward(args)...); this->setConstructed(true); } // In-place construction with possible destruction of the old value // initializer-list version template>>> void emplace(std::initializer_list il) { if (this->constructed()) { destruct(); } new (&get()) T(il); this->setConstructed(true); } private: // A helper function to convert the internal raw storage to T& constexpr const T& get() const { return *reinterpret_cast( reinterpret_cast(&this->mStorage)); } // Same thing, mutable T& get() { return const_cast(const_cast(this)->get()); } // Shortcut for a destructor call for the stored object void destruct() { get().T::~T(); } }; template Optional::type> makeOptional(T&& t) { return Optional::type>(std::forward(t)); } template Optional::type> makeOptional(Args&&... args) { return Optional::type>(kInplace, std::forward(args)...); } template bool operator==(const Optional& l, const Optional& r) { return l.hasValue() ? r.hasValue() && *l == *r : !r.hasValue(); } template bool operator==(const Optional& l, NulloptT) { return !l; } template bool operator==(NulloptT, const Optional& r) { return !r; } template bool operator==(const Optional& l, const T& r) { return bool(l) && *l == r; } template bool operator==(const T& l, const Optional& r) { return bool(r) && l == *r; } template bool operator!=(const Optional& l, const Optional& r) { return !(l == r); } template bool operator!=(const Optional& l, NulloptT) { return bool(l); } template bool operator!=(NulloptT, const Optional& r) { return bool(r); } template bool operator!=(const Optional& l, const T& r) { return !l || !(*l == r); } template bool operator!=(const T& l, const Optional& r) { return !r || !(l == *r); } template bool operator<(const Optional& l, const Optional& r) { return !r ? false : (!l ? true : *l < *r); } template bool operator<(const Optional&, NulloptT) { return false; } template bool operator<(NulloptT, const Optional& r) { return bool(r); } template bool operator<(const Optional& l, const T& r) { return !l || *l < r; } template bool operator<(const T& l, const Optional& r) { return bool(r) && l < *r; } } // namespace base } // namespace astc_codec #endif // ASTC_CODEC_BASE_OPTIONAL_H_