Commit 99d6990e authored by Phil Willoughby's avatar Phil Willoughby Committed by Facebook Github Bot

Update Optional

Summary: Now const-optimizer safe, and safe when the contained value overloads unary operator&

Reviewed By: yfeldblum

Differential Revision: D5480170

fbshipit-source-id: 3b53b0b6ce608857aa29d3f61eccd0b793b4cddc
parent 215da678
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
* cout << *v << endl; * cout << *v << endl;
* } * }
*/ */
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <new> #include <new>
...@@ -60,6 +61,7 @@ ...@@ -60,6 +61,7 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <folly/Launder.h>
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/Utility.h> #include <folly/Utility.h>
...@@ -101,14 +103,14 @@ class Optional { ...@@ -101,14 +103,14 @@ class Optional {
Optional(const Optional& src) noexcept( Optional(const Optional& src) noexcept(
std::is_nothrow_copy_constructible<Value>::value) { std::is_nothrow_copy_constructible<Value>::value) {
if (src.hasValue()) { if (src.hasValue()) {
construct(src.value()); storage_.construct(src.value());
} }
} }
Optional(Optional&& src) noexcept( Optional(Optional&& src) noexcept(
std::is_nothrow_move_constructible<Value>::value) { std::is_nothrow_move_constructible<Value>::value) {
if (src.hasValue()) { if (src.hasValue()) {
construct(std::move(src.value())); storage_.construct(std::move(src.value()));
src.clear(); src.clear();
} }
} }
...@@ -117,18 +119,18 @@ class Optional { ...@@ -117,18 +119,18 @@ class Optional {
/* implicit */ Optional(Value&& newValue) noexcept( /* implicit */ Optional(Value&& newValue) noexcept(
std::is_nothrow_move_constructible<Value>::value) { std::is_nothrow_move_constructible<Value>::value) {
construct(std::move(newValue)); storage_.construct(std::move(newValue));
} }
/* implicit */ Optional(const Value& newValue) noexcept( /* implicit */ Optional(const Value& newValue) noexcept(
std::is_nothrow_copy_constructible<Value>::value) { std::is_nothrow_copy_constructible<Value>::value) {
construct(newValue); storage_.construct(newValue);
} }
template <typename... Args> template <typename... Args>
explicit Optional(in_place_t, Args&&... args) noexcept( explicit Optional(in_place_t, Args&&... args) noexcept(
std::is_nothrow_constructible<Value, Args...>::value) { std::is_nothrow_constructible<Value, Args...>::value) {
construct(std::forward<Args>(args)...); storage_.construct(std::forward<Args>(args)...);
} }
void assign(const None&) { void assign(const None&) {
...@@ -156,17 +158,17 @@ class Optional { ...@@ -156,17 +158,17 @@ class Optional {
void assign(Value&& newValue) { void assign(Value&& newValue) {
if (hasValue()) { if (hasValue()) {
storage_.value = std::move(newValue); *storage_.value_pointer() = std::move(newValue);
} else { } else {
construct(std::move(newValue)); storage_.construct(std::move(newValue));
} }
} }
void assign(const Value& newValue) { void assign(const Value& newValue) {
if (hasValue()) { if (hasValue()) {
storage_.value = newValue; *storage_.value_pointer() = newValue;
} else { } else {
construct(newValue); storage_.construct(newValue);
} }
} }
...@@ -191,7 +193,7 @@ class Optional { ...@@ -191,7 +193,7 @@ class Optional {
template <class... Args> template <class... Args>
void emplace(Args&&... args) { void emplace(Args&&... args) {
clear(); clear();
construct(std::forward<Args>(args)...); storage_.construct(std::forward<Args>(args)...);
} }
void clear() { void clear() {
...@@ -200,34 +202,34 @@ class Optional { ...@@ -200,34 +202,34 @@ class Optional {
const Value& value() const & { const Value& value() const & {
require_value(); require_value();
return storage_.value; return *storage_.value_pointer();
} }
Value& value() & { Value& value() & {
require_value(); require_value();
return storage_.value; return *storage_.value_pointer();
} }
Value&& value() && { Value&& value() && {
require_value(); require_value();
return std::move(storage_.value); return std::move(*storage_.value_pointer());
} }
const Value&& value() const && { const Value&& value() const && {
require_value(); require_value();
return std::move(storage_.value); return std::move(*storage_.value_pointer());
} }
const Value* get_pointer() const & { const Value* get_pointer() const & {
return storage_.hasValue ? &storage_.value : nullptr; return storage_.value_pointer();
} }
Value* get_pointer() & { Value* get_pointer() & {
return storage_.hasValue ? &storage_.value : nullptr; return storage_.value_pointer();
} }
Value* get_pointer() && = delete; Value* get_pointer() && = delete;
bool hasValue() const { bool hasValue() const {
return storage_.hasValue; return storage_.hasValue();
} }
explicit operator bool() const { explicit operator bool() const {
...@@ -257,8 +259,8 @@ class Optional { ...@@ -257,8 +259,8 @@ class Optional {
// Return a copy of the value if set, or a given default if not. // Return a copy of the value if set, or a given default if not.
template <class U> template <class U>
Value value_or(U&& dflt) const & { Value value_or(U&& dflt) const & {
if (storage_.hasValue) { if (storage_.hasValue()) {
return storage_.value; return *storage_.value_pointer();
} }
return std::forward<U>(dflt); return std::forward<U>(dflt);
...@@ -266,8 +268,8 @@ class Optional { ...@@ -266,8 +268,8 @@ class Optional {
template <class U> template <class U>
Value value_or(U&& dflt) && { Value value_or(U&& dflt) && {
if (storage_.hasValue) { if (storage_.hasValue()) {
return std::move(storage_.value); return std::move(*storage_.value_pointer());
} }
return std::forward<U>(dflt); return std::forward<U>(dflt);
...@@ -275,73 +277,75 @@ class Optional { ...@@ -275,73 +277,75 @@ class Optional {
private: private:
void require_value() const { void require_value() const {
if (!storage_.hasValue) { if (!storage_.hasValue()) {
detail::throw_optional_empty_exception(); detail::throw_optional_empty_exception();
} }
} }
template <class... Args>
void construct(Args&&... args) {
const void* ptr = &storage_.value;
// for supporting const types
new (const_cast<void*>(ptr)) Value(std::forward<Args>(args)...);
storage_.hasValue = true;
}
struct StorageTriviallyDestructible { struct StorageTriviallyDestructible {
// The union trick allows to initialize the Optional's memory, protected:
// so that compiler/tools don't complain about uninitialized memory, bool hasValue_;
// without actually calling Value's default constructor. std::aligned_storage_t<sizeof(Value), alignof(Value)> value_[1];
// The rest of the implementation enforces that hasValue/value are
// synchronized.
union {
bool hasValue;
struct {
bool paddingForHasValue_[1];
Value value;
};
};
StorageTriviallyDestructible() : hasValue{false} {}
public:
StorageTriviallyDestructible() : hasValue_{false} {}
void clear() { void clear() {
hasValue = false; hasValue_ = false;
} }
}; };
struct StorageNonTriviallyDestructible { struct StorageNonTriviallyDestructible {
// See StorageTriviallyDestructible's union protected:
union { bool hasValue_;
bool hasValue; std::aligned_storage_t<sizeof(Value), alignof(Value)> value_[1];
struct {
bool paddingForHasValue_[1];
Value value;
};
};
FOLLY_PUSH_WARNING public:
// These are both informational warnings, but they trigger rare enough StorageNonTriviallyDestructible() : hasValue_{false} {}
// that we've left them enabled.
FOLLY_MSVC_DISABLE_WARNING(4587) // constructor of .value is not called
FOLLY_MSVC_DISABLE_WARNING(4588) // destructor of .value is not called
StorageNonTriviallyDestructible() : hasValue{false} {}
~StorageNonTriviallyDestructible() { ~StorageNonTriviallyDestructible() {
clear(); clear();
} }
FOLLY_POP_WARNING
void clear() { void clear() {
if (hasValue) { if (hasValue_) {
hasValue = false; hasValue_ = false;
value.~Value(); launder(reinterpret_cast<Value*>(value_))->~Value();
} }
} }
}; };
using Storage = typename std::conditional< struct Storage : std::conditional_t<
std::is_trivially_destructible<Value>::value, std::is_trivially_destructible<Value>::value,
StorageTriviallyDestructible, StorageTriviallyDestructible,
StorageNonTriviallyDestructible>::type; StorageNonTriviallyDestructible> {
bool hasValue() const {
return this->hasValue_;
}
Value* value_pointer() {
if (this->hasValue_) {
return launder(reinterpret_cast<Value*>(this->value_));
}
return nullptr;
}
Value const* value_pointer() const {
if (this->hasValue_) {
return launder(reinterpret_cast<Value const*>(this->value_));
}
return nullptr;
}
template <class... Args>
void construct(Args&&... args) {
new (raw_pointer()) Value(std::forward<Args>(args)...);
this->hasValue_ = true;
}
private:
void* raw_pointer() {
return static_cast<void*>(this->value_);
}
};
Storage storage_; Storage storage_;
}; };
...@@ -367,7 +371,7 @@ void swap(Optional<T>& a, Optional<T>& b) { ...@@ -367,7 +371,7 @@ void swap(Optional<T>& a, Optional<T>& b) {
} }
} }
template <class T, class Opt = Optional<typename std::decay<T>::type>> template <class T, class Opt = Optional<std::decay_t<T>>>
Opt make_optional(T&& v) { Opt make_optional(T&& v) {
return Opt(std::forward<T>(v)); return Opt(std::forward<T>(v));
} }
...@@ -467,7 +471,7 @@ struct hash<folly::Optional<T>> { ...@@ -467,7 +471,7 @@ struct hash<folly::Optional<T>> {
if (!obj.hasValue()) { if (!obj.hasValue()) {
return 0; return 0;
} }
return hash<typename remove_const<T>::type>()(*obj); return hash<remove_const_t<T>>()(*obj);
} }
}; };
FOLLY_NAMESPACE_STD_END FOLLY_NAMESPACE_STD_END
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment