/************************************************************************* * * Copyright 2016 Realm Inc. * * 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 #ifndef REALM_UTIL_OPTIONAL_HPP #define REALM_UTIL_OPTIONAL_HPP #include #include #include // std::logic_error #include // std::less namespace realm { namespace util { template class Optional; // some() should be the equivalent of the proposed C++17 `make_optional`. template Optional some(Args&&...); template struct Some; // Note: Should conform with the future std::nullopt_t and std::in_place_t. struct None { constexpr explicit None(int) { } }; static constexpr None none{0}; struct InPlace { constexpr InPlace() { } }; static constexpr InPlace in_place; // Note: Should conform with the future std::bad_optional_access. struct BadOptionalAccess : ExceptionWithBacktrace { using ExceptionWithBacktrace::ExceptionWithBacktrace; }; } // namespace util namespace _impl { template ::value> struct OptionalStorage; template struct TypeIsAssignableToOptional { // Constraints from [optional.object.assign.18] static const bool value = (std::is_same::type, T>::value && std::is_constructible::value && std::is_assignable::value); }; } // namespace _impl namespace util { // Note: Should conform with the future std::optional. template class Optional : private realm::_impl::OptionalStorage { public: using value_type = T; constexpr Optional(); constexpr Optional(None); Optional(Optional&& other) noexcept; Optional(const Optional& other); constexpr Optional(T&& value); constexpr Optional(const T& value); template constexpr Optional(InPlace tag, Args&&...); // FIXME: std::optional specifies an std::initializer_list constructor overload as well. Optional& operator=(None) noexcept; Optional& operator=(Optional&& other) noexcept(std::is_nothrow_move_assignable::value); Optional& operator=(const Optional& other) noexcept(std::is_nothrow_copy_assignable::value); template ::value>::type> Optional& operator=(U&& value); explicit constexpr operator bool() const; constexpr const T& value() const; // Throws T& value(); // Throws, FIXME: Can be constexpr with C++14 constexpr const T& operator*() const; // Throws T& operator*(); // Throws, FIXME: Can be constexpr with C++14 constexpr const T* operator->() const; // Throws T* operator->(); // Throws, FIXME: Can be constexpr with C++14 template constexpr T value_or(U&& value) const &; template T value_or(U&& value) &&; void swap(Optional& other); // FIXME: Add noexcept() clause template void emplace(Args&&...); // FIXME: std::optional specifies an std::initializer_list overload for `emplace` as well. void reset(); private: using Storage = realm::_impl::OptionalStorage; using Storage::m_engaged; using Storage::m_value; constexpr bool is_engaged() const { return m_engaged; } void set_engaged(bool b) { m_engaged = b; } }; /// An Optional is functionally equivalent to a bool. /// Note: C++17 does not (yet) specify this specialization, but it is convenient /// as a "safer bool", especially in the presence of `fmap`. /// Disabled for compliance with std::optional. // template <> // class Optional { // public: // Optional() {} // Optional(None) {} // Optional(Optional&&) = default; // Optional(const Optional&) = default; // explicit operator bool() const { return m_engaged; } // private: // bool m_engaged = false; // friend struct Some; // }; /// An Optional is a non-owning nullable pointer that throws on dereference. // FIXME: Visual Studio 2015's constexpr support isn't sufficient to allow Optional to compile // in constexpr contexts. template class Optional { public: using value_type = T&; using target_type = typename std::decay::type; constexpr Optional() { } constexpr Optional(None) { } // FIXME: Was a delegating constructor, but not fully supported in VS2015 Optional(const Optional& other) = default; template Optional(const Optional& other) noexcept : m_ptr(other.m_ptr) { } template Optional(std::reference_wrapper ref) noexcept : m_ptr(&ref.get()) { } constexpr Optional(T& init_value) noexcept : m_ptr(&init_value) { } Optional(T&& value) = delete; // Catches accidental references to rvalue temporaries. Optional& operator=(None) noexcept { m_ptr = nullptr; return *this; } Optional& operator=(const Optional& other) { m_ptr = other.m_ptr; return *this; } template Optional& operator=(std::reference_wrapper ref) noexcept { m_ptr = &ref.get(); return *this; } explicit constexpr operator bool() const noexcept { return m_ptr; } constexpr const target_type& value() const; // Throws target_type& value(); // Throws constexpr const target_type& operator*() const { return value(); } target_type& operator*() { return value(); } constexpr const target_type* operator->() const { return &value(); } target_type* operator->() { return &value(); } void swap(Optional other); // FIXME: Add noexcept() clause private: T* m_ptr = nullptr; template friend class Optional; }; template struct RemoveOptional { using type = T; }; template struct RemoveOptional> { using type = typename RemoveOptional::type; // Remove recursively }; /// Implementation: template struct Some { template static Optional some(Args&&... args) { return Optional{std::forward(args)...}; } }; /// Disabled for compliance with std::optional. // template <> // struct Some { // static Optional some() // { // Optional opt; // opt.m_engaged = true; // return opt; // } // }; template Optional some(Args&&... args) { return Some::some(std::forward(args)...); } template constexpr Optional::Optional() : Storage(none) { } template constexpr Optional::Optional(None) : Storage(none) { } template Optional::Optional(Optional&& other) noexcept : Storage(none) { if (other.m_engaged) { new (&m_value) T(std::move(other.m_value)); m_engaged = true; } } template Optional::Optional(const Optional& other) : Storage(none) { if (other.m_engaged) { new (&m_value) T(other.m_value); m_engaged = true; } } template constexpr Optional::Optional(T&& r_value) : Storage(std::move(r_value)) { } template constexpr Optional::Optional(const T& l_value) : Storage(l_value) { } template template constexpr Optional::Optional(InPlace, Args&&... args) : Storage(std::forward(args)...) { } template void Optional::reset() { if (m_engaged) { m_value.~T(); m_engaged = false; } } template Optional& Optional::operator=(None) noexcept { reset(); return *this; } template Optional& Optional::operator=(Optional&& other) noexcept(std::is_nothrow_move_assignable::value) { if (m_engaged) { if (other.m_engaged) { m_value = std::move(other.m_value); } else { reset(); } } else { if (other.m_engaged) { new (&m_value) T(std::move(other.m_value)); m_engaged = true; } } return *this; } template Optional& Optional::operator=(const Optional& other) noexcept(std::is_nothrow_copy_assignable::value) { if (m_engaged) { if (other.m_engaged) { m_value = other.m_value; } else { reset(); } } else { if (other.m_engaged) { new (&m_value) T(other.m_value); m_engaged = true; } } return *this; } template template Optional& Optional::operator=(U&& r_value) { if (m_engaged) { m_value = std::forward(r_value); } else { new (&m_value) T(std::forward(r_value)); m_engaged = true; } return *this; } template constexpr Optional::operator bool() const { return m_engaged; } template constexpr const T& Optional::value() const { return m_value; } template T& Optional::value() { REALM_ASSERT(m_engaged); return m_value; } template constexpr const typename Optional::target_type& Optional::value() const { return *m_ptr; } template typename Optional::target_type& Optional::value() { REALM_ASSERT(m_ptr); return *m_ptr; } template constexpr const T& Optional::operator*() const { return value(); } template T& Optional::operator*() { return value(); } template constexpr const T* Optional::operator->() const { return &value(); } template T* Optional::operator->() { return &value(); } template template constexpr T Optional::value_or(U&& otherwise) const & { return m_engaged ? T{m_value} : T{std::forward(otherwise)}; } template template T Optional::value_or(U&& otherwise) && { if (is_engaged()) { return T(std::move(m_value)); } else { return T(std::forward(otherwise)); } } template void Optional::swap(Optional& other) { // FIXME: This might be optimizable. Optional tmp = std::move(other); other = std::move(*this); *this = std::move(tmp); } template template void Optional::emplace(Args&&... args) { reset(); new (&m_value) T(std::forward(args)...); m_engaged = true; } template constexpr Optional::type> make_optional(T&& value) { using Type = typename std::decay::type; return some(std::forward(value)); } template bool operator==(const Optional& lhs, const Optional& rhs) { if (!lhs && !rhs) { return true; } if (lhs && rhs) { return *lhs == *rhs; } return false; } template bool operator!=(const Optional& lhs, const Optional& rhs) { return !(lhs == rhs); } template bool operator<(const Optional& lhs, const Optional& rhs) { if (!rhs) { return false; } if (!lhs) { return true; } return std::less{}(*lhs, *rhs); } template bool operator>(const util::Optional& lhs, const util::Optional& rhs) { if (!lhs) { return false; } if (!rhs) { return true; } return std::greater{}(*lhs, *rhs); } template bool operator==(const Optional& lhs, None) { return !bool(lhs); } template bool operator!=(const Optional& lhs, None) { return bool(lhs); } template bool operator<(const Optional& lhs, None) { static_cast(lhs); return false; } template bool operator==(None, const Optional& rhs) { return !bool(rhs); } template bool operator!=(None, const Optional& rhs) { return bool(rhs); } template bool operator<(None, const Optional& rhs) { return bool(rhs); } template bool operator==(const Optional& lhs, const U& rhs) { return lhs ? *lhs == rhs : false; } template bool operator<(const Optional& lhs, const T& rhs) { return lhs ? std::less{}(*lhs, rhs) : true; } template bool operator==(const T& lhs, const Optional& rhs) { return rhs ? lhs == *rhs : false; } template bool operator<(const T& lhs, const Optional& rhs) { return rhs ? std::less{}(lhs, *rhs) : false; } template auto operator>>(Optional lhs, F&& rhs) -> decltype(fmap(lhs, std::forward(rhs))) { return fmap(lhs, std::forward(rhs)); } template OS& operator<<(OS& os, const Optional& rhs) { if (rhs) { os << "some(" << *rhs << ")"; } else { os << "none"; } return os; } template T unwrap(T&& value) { return value; } template T unwrap(util::Optional&& value) { return *value; } template T unwrap(const util::Optional& value) { return *value; } template T unwrap(util::Optional& value) { return *value; } } // namespace util namespace _impl { // T is trivially destructible. template struct OptionalStorage { union { T m_value; char m_null_state; }; bool m_engaged = false; constexpr OptionalStorage(realm::util::None) : m_null_state() { } constexpr OptionalStorage(T&& value) : m_value(std::move(value)) , m_engaged(true) { } template constexpr OptionalStorage(Args&&... args) : m_value(args...) , m_engaged(true) { } }; // T is not trivially destructible. template struct OptionalStorage { union { T m_value; char m_null_state; }; bool m_engaged = false; constexpr OptionalStorage(realm::util::None) : m_null_state() { } constexpr OptionalStorage(T&& value) : m_value(std::move(value)) , m_engaged(true) { } template constexpr OptionalStorage(Args&&... args) : m_value(args...) , m_engaged(true) { } ~OptionalStorage() { if (m_engaged) m_value.~T(); } }; } // namespace _impl using util::none; } // namespace realm // for convienence, inject a default hash implementation into the std namespace namespace std { template struct hash> { std::size_t operator()(realm::util::Optional const& o) const noexcept { if (bool(o) == false) { return 0; // any choice will collide with some std::hash } else { return std::hash{}(*o); } } }; } #endif // REALM_UTIL_OPTIONAL_HPP