X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FOptional.h;h=60ae2c735b1152ca2c883263c483b4ca229af386;hp=eb8de160c8903d47bda5757c4e28b783bd402ae1;hb=0fd994133fbaafa1e4b6c86f5c14a101f2086e8c;hpb=cb2dc2c8e4fefcc3dd38ed90e2aca02bc1e82e2c diff --git a/folly/Optional.h b/folly/Optional.h index eb8de160..60ae2c73 100644 --- a/folly/Optional.h +++ b/folly/Optional.h @@ -1,5 +1,5 @@ /* - * Copyright 2014 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef FOLLY_OPTIONAL_H_ -#define FOLLY_OPTIONAL_H_ +#pragma once /* * Optional - For conditional initialization of values, like boost::optional, @@ -54,81 +53,97 @@ * cout << *v << endl; * } */ -#include -#include + #include +#include +#include +#include #include - -#include +#include #include +#include +#include +#include namespace folly { -namespace detail { struct NoneHelper {}; } +template +class Optional; + +namespace detail { +struct NoneHelper {}; + +// Allow each translation unit to control its own -fexceptions setting. +// If exceptions are disabled, std::terminate() will be called instead of +// throwing OptionalEmptyException when the condition fails. +[[noreturn]] void throw_optional_empty_exception(); + +template +struct OptionalPromiseReturn; +} // namespace detail typedef int detail::NoneHelper::*None; const None none = nullptr; -/** - * gcc-4.7 warns about use of uninitialized memory around the use of storage_ - * even though this is explicitly initialized at each point. - */ -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wuninitialized" -# pragma GCC diagnostic ignored "-Wpragmas" -# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif // __GNUC__ - -template +class OptionalEmptyException : public std::runtime_error { + public: + OptionalEmptyException() + : std::runtime_error("Empty Optional cannot be unwrapped") {} +}; + +template class Optional { public: - static_assert(!std::is_reference::value, - "Optional may not be used with reference types"); + typedef Value value_type; - Optional() - : hasValue_(false) { - } + static_assert( + !std::is_reference::value, + "Optional may not be used with reference types"); + static_assert( + !std::is_abstract::value, + "Optional may not be used with abstract types"); - Optional(const Optional& src) - noexcept(std::is_nothrow_copy_constructible::value) { + FOLLY_CPP14_CONSTEXPR Optional() noexcept {} + Optional(const Optional& src) noexcept( + std::is_nothrow_copy_constructible::value) { if (src.hasValue()) { - construct(src.value()); - } else { - hasValue_ = false; + storage_.construct(src.value()); } } - Optional(Optional&& src) - noexcept(std::is_nothrow_move_constructible::value) { - + Optional(Optional&& src) noexcept( + std::is_nothrow_move_constructible::value) { if (src.hasValue()) { - construct(std::move(src.value())); + storage_.construct(std::move(src.value())); src.clear(); - } else { - hasValue_ = false; } } - /* implicit */ Optional(const None&) noexcept - : hasValue_(false) { + FOLLY_CPP14_CONSTEXPR /* implicit */ Optional(const None&) noexcept {} + + FOLLY_CPP14_CONSTEXPR /* implicit */ Optional(Value&& newValue) noexcept( + std::is_nothrow_move_constructible::value) { + storage_.construct(std::move(newValue)); } - /* implicit */ Optional(Value&& newValue) - noexcept(std::is_nothrow_move_constructible::value) { - construct(std::move(newValue)); + FOLLY_CPP14_CONSTEXPR /* implicit */ Optional(const Value& newValue) noexcept( + std::is_nothrow_copy_constructible::value) { + storage_.construct(newValue); } - /* implicit */ Optional(const Value& newValue) - noexcept(std::is_nothrow_copy_constructible::value) { - construct(newValue); + template + FOLLY_CPP14_CONSTEXPR explicit Optional(in_place_t, Args&&... args) noexcept( + std::is_nothrow_constructible::value) { + storage_.construct(std::forward(args)...); } - ~Optional() noexcept { - clear(); + // Used only when an Optional is used with coroutines on MSVC + /* implicit */ Optional(const detail::OptionalPromiseReturn& p) + : Optional{} { + p.promise_->value_ = this; } void assign(const None&) { @@ -156,168 +171,486 @@ class Optional { void assign(Value&& newValue) { if (hasValue()) { - value_ = std::move(newValue); + *storage_.value_pointer() = std::move(newValue); } else { - construct(std::move(newValue)); + storage_.construct(std::move(newValue)); } } void assign(const Value& newValue) { if (hasValue()) { - value_ = newValue; + *storage_.value_pointer() = newValue; } else { - construct(newValue); + storage_.construct(newValue); } } - template + template Optional& operator=(Arg&& arg) { assign(std::forward(arg)); return *this; } - Optional& operator=(Optional &&other) - noexcept (std::is_nothrow_move_assignable::value) { - + Optional& operator=(Optional&& other) noexcept( + std::is_nothrow_move_assignable::value) { assign(std::move(other)); return *this; } - Optional& operator=(const Optional &other) - noexcept (std::is_nothrow_copy_assignable::value) { - + Optional& operator=(const Optional& other) noexcept( + std::is_nothrow_copy_assignable::value) { assign(other); return *this; } - template - void emplace(Args&&... args) { + template + Value& emplace(Args&&... args) { clear(); - construct(std::forward(args)...); + return storage_.construct(std::forward(args)...); } - void clear() { - if (hasValue()) { - hasValue_ = false; - value_.~Value(); + template + typename std::enable_if< + std::is_constructible&, Args&&...>::value, + Value&>::type + emplace(std::initializer_list ilist, Args&&... args) { + clear(); + return storage_.construct(ilist, std::forward(args)...); + } + + void reset() noexcept { + storage_.clear(); + } + + void clear() noexcept { + reset(); + } + + void swap(Optional& that) noexcept(IsNothrowSwappable::value) { + if (hasValue() && that.hasValue()) { + using std::swap; + swap(value(), that.value()); + } else if (hasValue()) { + that.emplace(std::move(value())); + reset(); + } else if (that.hasValue()) { + emplace(std::move(that.value())); + that.reset(); } } - const Value& value() const { - assert(hasValue()); - return value_; + FOLLY_CPP14_CONSTEXPR const Value& value() const & { + require_value(); + return *storage_.value_pointer(); } - Value& value() { - assert(hasValue()); - return value_; + FOLLY_CPP14_CONSTEXPR Value& value() & { + require_value(); + return *storage_.value_pointer(); } - bool hasValue() const { return hasValue_; } + FOLLY_CPP14_CONSTEXPR Value&& value() && { + require_value(); + return std::move(*storage_.value_pointer()); + } - explicit operator bool() const { - return hasValue(); + FOLLY_CPP14_CONSTEXPR const Value&& value() const && { + require_value(); + return std::move(*storage_.value_pointer()); } - const Value& operator*() const { return value(); } - Value& operator*() { return value(); } + const Value* get_pointer() const & { + return storage_.value_pointer(); + } + Value* get_pointer() & { + return storage_.value_pointer(); + } + Value* get_pointer() && = delete; + + FOLLY_CPP14_CONSTEXPR bool has_value() const noexcept { + return storage_.hasValue(); + } - const Value* operator->() const { return &value(); } - Value* operator->() { return &value(); } + FOLLY_CPP14_CONSTEXPR bool hasValue() const noexcept { + return has_value(); + } + + FOLLY_CPP14_CONSTEXPR explicit operator bool() const noexcept { + return has_value(); + } + + FOLLY_CPP14_CONSTEXPR const Value& operator*() const & { + return value(); + } + FOLLY_CPP14_CONSTEXPR Value& operator*() & { + return value(); + } + FOLLY_CPP14_CONSTEXPR const Value&& operator*() const && { + return std::move(value()); + } + FOLLY_CPP14_CONSTEXPR Value&& operator*() && { + return std::move(value()); + } + + FOLLY_CPP14_CONSTEXPR const Value* operator->() const { + return &value(); + } + FOLLY_CPP14_CONSTEXPR Value* operator->() { + return &value(); + } + + // Return a copy of the value if set, or a given default if not. + template + FOLLY_CPP14_CONSTEXPR Value value_or(U&& dflt) const & { + if (storage_.hasValue()) { + return *storage_.value_pointer(); + } + + return std::forward(dflt); + } + + template + FOLLY_CPP14_CONSTEXPR Value value_or(U&& dflt) && { + if (storage_.hasValue()) { + return std::move(*storage_.value_pointer()); + } + + return std::forward(dflt); + } private: - template - void construct(Args&&... args) { - const void* ptr = &value_; - // for supporting const types - new(const_cast(ptr)) Value(std::forward(args)...); - hasValue_ = true; + void require_value() const { + if (!storage_.hasValue()) { + detail::throw_optional_empty_exception(); + } } - // uninitialized - union { Value value_; }; - bool hasValue_; -}; + struct StorageTriviallyDestructible { + protected: + bool hasValue_; + typename std::aligned_storage::type + value_[1]; + + public: + StorageTriviallyDestructible() : hasValue_{false} {} + void clear() { + hasValue_ = false; + } + }; -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif + struct StorageNonTriviallyDestructible { + protected: + bool hasValue_; + typename std::aligned_storage::type + value_[1]; -template + public: + StorageNonTriviallyDestructible() : hasValue_{false} {} + ~StorageNonTriviallyDestructible() { + clear(); + } + + void clear() { + if (hasValue_) { + hasValue_ = false; + launder(reinterpret_cast(value_))->~Value(); + } + } + }; + + struct Storage : std::conditional< + std::is_trivially_destructible::value, + StorageTriviallyDestructible, + StorageNonTriviallyDestructible>::type { + bool hasValue() const noexcept { + return this->hasValue_; + } + + Value* value_pointer() { + if (this->hasValue_) { + return launder(reinterpret_cast(this->value_)); + } + return nullptr; + } + + Value const* value_pointer() const { + if (this->hasValue_) { + return launder(reinterpret_cast(this->value_)); + } + return nullptr; + } + + template + Value& construct(Args&&... args) { + new (raw_pointer()) Value(std::forward(args)...); + this->hasValue_ = true; + return *launder(reinterpret_cast(this->value_)); + } + + private: + void* raw_pointer() { + return static_cast(this->value_); + } + }; + + Storage storage_; +}; + +template const T* get_pointer(const Optional& opt) { - return opt ? &opt.value() : nullptr; + return opt.get_pointer(); } -template +template T* get_pointer(Optional& opt) { - return opt ? &opt.value() : nullptr; + return opt.get_pointer(); } -template -void swap(Optional& a, Optional& b) { - if (a.hasValue() && b.hasValue()) { - // both full - using std::swap; - swap(a.value(), b.value()); - } else if (a.hasValue() || b.hasValue()) { - std::swap(a, b); // fall back to default implementation if they're mixed. - } +template +void swap(Optional& a, Optional& b) noexcept(noexcept(a.swap(b))) { + a.swap(b); } -template::type>> -Opt make_optional(T&& v) { +template ::type>> +constexpr Opt make_optional(T&& v) { return Opt(std::forward(v)); } -template -bool operator< (const Optional& a, const Optional& b) { - if (a.hasValue() != b.hasValue()) { return a.hasValue() < b.hasValue(); } - if (a.hasValue()) { return a.value() < b.value(); } - return false; +/////////////////////////////////////////////////////////////////////////////// +// Comparisons. + +template +constexpr bool operator==(const Optional& a, const V& b) { + return a.hasValue() && a.value() == b; +} + +template +constexpr bool operator!=(const Optional& a, const V& b) { + return !(a == b); +} + +template +constexpr bool operator==(const U& a, const Optional& b) { + return b.hasValue() && b.value() == a; } -template -bool operator==(const Optional& a, const Optional& b) { - if (a.hasValue() != b.hasValue()) { return false; } - if (a.hasValue()) { return a.value() == b.value(); } +template +constexpr bool operator!=(const U& a, const Optional& b) { + return !(a == b); +} + +template +FOLLY_CPP14_CONSTEXPR bool operator==( + const Optional& a, + const Optional& b) { + if (a.hasValue() != b.hasValue()) { + return false; + } + if (a.hasValue()) { + return a.value() == b.value(); + } return true; } -template -bool operator<=(const Optional& a, const Optional& b) { - return !(b < a); +template +constexpr bool operator!=(const Optional& a, const Optional& b) { + return !(a == b); } -template -bool operator!=(const Optional& a, const Optional& b) { - return !(b == a); +template +FOLLY_CPP14_CONSTEXPR bool operator<( + const Optional& a, + const Optional& b) { + if (a.hasValue() != b.hasValue()) { + return a.hasValue() < b.hasValue(); + } + if (a.hasValue()) { + return a.value() < b.value(); + } + return false; } -template -bool operator>=(const Optional& a, const Optional& b) { +template +constexpr bool operator>(const Optional& a, const Optional& b) { + return b < a; +} + +template +constexpr bool operator<=(const Optional& a, const Optional& b) { + return !(b < a); +} + +template +constexpr bool operator>=(const Optional& a, const Optional& b) { return !(a < b); } -template -bool operator> (const Optional& a, const Optional& b) { - return b < a; +// Suppress comparability of Optional with T, despite implicit conversion. +template +bool operator<(const Optional&, const V& other) = delete; +template +bool operator<=(const Optional&, const V& other) = delete; +template +bool operator>=(const Optional&, const V& other) = delete; +template +bool operator>(const Optional&, const V& other) = delete; +template +bool operator<(const V& other, const Optional&) = delete; +template +bool operator<=(const V& other, const Optional&) = delete; +template +bool operator>=(const V& other, const Optional&) = delete; +template +bool operator>(const V& other, const Optional&) = delete; + +// Comparisons with none +template +constexpr bool operator==(const Optional& a, None) noexcept { + return !a.hasValue(); +} +template +constexpr bool operator==(None, const Optional& a) noexcept { + return !a.hasValue(); +} +template +constexpr bool operator<(const Optional&, None) noexcept { + return false; +} +template +constexpr bool operator<(None, const Optional& a) noexcept { + return a.hasValue(); +} +template +constexpr bool operator>(const Optional& a, None) noexcept { + return a.hasValue(); +} +template +constexpr bool operator>(None, const Optional&) noexcept { + return false; +} +template +constexpr bool operator<=(None, const Optional&) noexcept { + return true; +} +template +constexpr bool operator<=(const Optional& a, None) noexcept { + return !a.hasValue(); +} +template +constexpr bool operator>=(const Optional&, None) noexcept { + return true; +} +template +constexpr bool operator>=(None, const Optional& a) noexcept { + return !a.hasValue(); } -// To supress comparability of Optional with T, despite implicit conversion. -template bool operator< (const Optional&, const V& other) = delete; -template bool operator<=(const Optional&, const V& other) = delete; -template bool operator==(const Optional&, const V& other) = delete; -template bool operator!=(const Optional&, const V& other) = delete; -template bool operator>=(const Optional&, const V& other) = delete; -template bool operator> (const Optional&, const V& other) = delete; -template bool operator< (const V& other, const Optional&) = delete; -template bool operator<=(const V& other, const Optional&) = delete; -template bool operator==(const V& other, const Optional&) = delete; -template bool operator!=(const V& other, const Optional&) = delete; -template bool operator>=(const V& other, const Optional&) = delete; -template bool operator> (const V& other, const Optional&) = delete; +/////////////////////////////////////////////////////////////////////////////// } // namespace folly -#endif//FOLLY_OPTIONAL_H_ +// Allow usage of Optional in std::unordered_map and std::unordered_set +FOLLY_NAMESPACE_STD_BEGIN +template +struct hash> { + size_t operator()(folly::Optional const& obj) const { + if (!obj.hasValue()) { + return 0; + } + return hash::type>()(*obj); + } +}; +FOLLY_NAMESPACE_STD_END + +// Enable the use of folly::Optional with `co_await` +// Inspired by https://github.com/toby-allsopp/coroutine_monad +#if FOLLY_HAS_COROUTINES +#include + +namespace folly { +namespace detail { +template +struct OptionalPromise; + +template +struct OptionalPromiseReturn { + Optional storage_; + OptionalPromise* promise_; + /* implicit */ OptionalPromiseReturn(OptionalPromise& promise) noexcept + : promise_(&promise) { + promise.value_ = &storage_; + } + OptionalPromiseReturn(OptionalPromiseReturn&& that) noexcept + : OptionalPromiseReturn{*that.promise_} {} + ~OptionalPromiseReturn() {} + /* implicit */ operator Optional() & { + return std::move(storage_); + } +}; + +template +struct OptionalPromise { + Optional* value_ = nullptr; + OptionalPromise() = default; + OptionalPromise(OptionalPromise const&) = delete; + // This should work regardless of whether the compiler generates: + // folly::Optional retobj{ p.get_return_object(); } // MSVC + // or: + // auto retobj = p.get_return_object(); // clang + OptionalPromiseReturn get_return_object() noexcept { + return *this; + } + std::experimental::suspend_never initial_suspend() const noexcept { + return {}; + } + std::experimental::suspend_never final_suspend() const { + return {}; + } + template + void return_value(U&& u) { + *value_ = static_cast(u); + } + void unhandled_exception() { + // Technically, throwing from unhandled_exception is underspecified: + // https://github.com/GorNishanov/CoroutineWording/issues/17 + throw; + } +}; + +template +struct OptionalAwaitable { + Optional o_; + bool await_ready() const noexcept { + return o_.hasValue(); + } + Value await_resume() { + return std::move(o_.value()); + } + + // Explicitly only allow suspension into an OptionalPromise + template + void await_suspend( + std::experimental::coroutine_handle> h) const { + // Abort the rest of the coroutine. resume() is not going to be called + h.destroy(); + } +}; +} // namespace detail + +template +detail::OptionalAwaitable +/* implicit */ operator co_await(Optional o) { + return {std::move(o)}; +} +} // namespace folly + +// This makes folly::Optional useable as a coroutine return type.. +namespace std { +namespace experimental { +template +struct coroutine_traits, Args...> { + using promise_type = folly::detail::OptionalPromise; +}; +} // namespace experimental +} // namespace std +#endif // FOLLY_HAS_COROUTINES