Chromium Code Reviews| Index: webrtc/base/optional.h |
| diff --git a/webrtc/base/optional.h b/webrtc/base/optional.h |
| index 25cfbfe41752597d340864f3cc46ce56794763b4..fc527081f19992072b96ccb04d90922cd866b3a2 100644 |
| --- a/webrtc/base/optional.h |
| +++ b/webrtc/base/optional.h |
| @@ -1,203 +1,465 @@ |
| -/* |
| - * Copyright 2015 The WebRTC Project Authors. All rights reserved. |
| - * |
| - * Use of this source code is governed by a BSD-style license |
| - * that can be found in the LICENSE file in the root of the source |
| - * tree. An additional intellectual property rights grant can be found |
| - * in the file PATENTS. All contributing project authors may |
| - * be found in the AUTHORS file in the root of the source tree. |
| - */ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
|
ossu
2016/06/17 14:51:48
I kept the original copyright message, since I don
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| #ifndef WEBRTC_BASE_OPTIONAL_H_ |
| #define WEBRTC_BASE_OPTIONAL_H_ |
| -#include <algorithm> |
| -#include <memory> |
| -#include <utility> |
| +#include <type_traits> |
| #include "webrtc/base/checks.h" |
| +#include "webrtc/base/template_util.h" |
| namespace rtc { |
| -// Simple std::optional-wannabe. It either contains a T or not. |
| -// |
| -// A moved-from Optional<T> may only be destroyed, and assigned to if T allows |
| -// being assigned to after having been moved from. Specifically, you may not |
| -// assume that it just doesn't contain a value anymore. |
| -// |
| -// Examples of good places to use Optional: |
| -// |
| -// - As a class or struct member, when the member doesn't always have a value: |
| -// struct Prisoner { |
| -// std::string name; |
| -// Optional<int> cell_number; // Empty if not currently incarcerated. |
| -// }; |
| -// |
| -// - As a return value for functions that may fail to return a value on all |
| -// allowed inputs. For example, a function that searches an array might |
| -// return an Optional<size_t> (the index where it found the element, or |
| -// nothing if it didn't find it); and a function that parses numbers might |
| -// return Optional<double> (the parsed number, or nothing if parsing failed). |
| -// |
| -// Examples of bad places to use Optional: |
| -// |
| -// - As a return value for functions that may fail because of disallowed |
| -// inputs. For example, a string length function should not return |
| -// Optional<size_t> so that it can return nothing in case the caller passed |
| -// it a null pointer; the function should probably use RTC_[D]CHECK instead, |
| -// and return plain size_t. |
| +// Specification: |
| +// http://en.cppreference.com/w/cpp/utility/optional/in_place_t |
| +struct in_place_t {}; |
| + |
| +// Specification: |
| +// http://en.cppreference.com/w/cpp/utility/optional/nullopt_t |
| +struct nullopt_t { |
| + constexpr explicit nullopt_t(int) {} |
| +}; |
| + |
| +// Specification: |
| +// http://en.cppreference.com/w/cpp/utility/optional/in_place |
| +constexpr in_place_t in_place = {}; |
| + |
| +// Specification: |
| +// http://en.cppreference.com/w/cpp/utility/optional/nullopt |
| +constexpr nullopt_t nullopt(0); |
| + |
| +namespace internal { |
| + |
| +template <typename T, bool = rtc::is_trivially_destructible<T>::value> |
| +struct OptionalStorage { |
| + OptionalStorage() {}; |
| + // When T is not trivially destructible we must call its |
| + // destructor before deallocating its memory. |
| + ~OptionalStorage() { |
| + if (!is_null_) |
| + value_.~T(); |
| + } |
| + |
| + bool is_null_ = true; |
| + union { |
| + T value_; |
| + }; |
| +}; |
| + |
| +template <typename T> |
| +struct OptionalStorage<T, true> { |
| + OptionalStorage() {}; |
| + // When T is trivially destructible (i.e. its destructor does nothing) |
| + // there is no need to call it. |
| + // Since |rtc::AlignedMemory| is just an array its destructor |
| + // is trivial. Explicitly defaulting the destructor means it's not |
| + // user-provided. All of this together make this destructor trivial. |
| + ~OptionalStorage() = default; |
| + |
| + bool is_null_ = true; |
| + union { |
| + T value_; |
| + }; |
| +}; |
| + |
| +} // namespace internal |
| + |
| +// rtc::Optional has been borrowed from Chromium's base::Optional, with some |
| +// alterations. It is a version of the C++17 optional class: |
| +// std::optional documentation: |
| +// http://en.cppreference.com/w/cpp/utility/optional Chromium documentation: |
| +// https://chromium.googlesource.com/chromium/src/+/master/docs/optional.md |
| // |
| -// - As a return value for functions that may fail to return a value on all |
| -// allowed inputs, but need to tell the caller what went wrong. Returning |
| -// Optional<double> when parsing a single number as in the example above |
| -// might make sense, but any larger parse job is probably going to need to |
| -// tell the caller what the problem was, not just that there was one. |
| +// These are the differences between the specification and the implementation: |
| +// - The constructor and emplace method using initializer_list are not |
| +// implemented because 'initializer_list' is banned from WebRTC. |
| +// - Constructors do not use 'constexpr' as it is a C++14 extension. |
| +// - 'constexpr' might be missing in some places for reasons specified locally. |
| +// - No exceptions are thrown, because they are banned from WebRTC. |
| +// - All the non-members are in the 'rtc' namespace instead of 'std'. |
| // |
| -// TODO(kwiberg): Get rid of this class when the standard library has |
| -// std::optional (and we're allowed to use it). |
| +// The WebRTC version uses this one neat union trick to implement |
| +// OptionalStorage rather than using Chromium's AlignedStorage. |
| template <typename T> |
| -class Optional final { |
| +class Optional { |
| public: |
| - // Construct an empty Optional. |
| - Optional() : has_value_(false) {} |
| + using value_type = T; |
| - // Construct an Optional that contains a value. |
| - explicit Optional(const T& value) : has_value_(true) { |
| - new (&value_) T(value); |
| - } |
| - explicit Optional(T&& value) : has_value_(true) { |
| - new (&value_) T(std::move(value)); |
| - } |
| + constexpr Optional() = default; |
| + Optional(rtc::nullopt_t) : Optional() {} |
| - // Copy constructor: copies the value from m if it has one. |
| - Optional(const Optional& m) : has_value_(m.has_value_) { |
| - if (has_value_) |
| - new (&value_) T(m.value_); |
| + Optional(const Optional& other) { |
| + if (!other.storage_.is_null_) |
| + Init(other.value()); |
| } |
| - // Move constructor: if m has a value, moves the value from m, leaving m |
| - // still in a state where it has a value, but a moved-from one (the |
| - // properties of which depends on T; the only general guarantee is that we |
| - // can destroy m). |
| - Optional(Optional&& m) : has_value_(m.has_value_) { |
| - if (has_value_) |
| - new (&value_) T(std::move(m.value_)); |
| + Optional(Optional&& other) { |
| + if (!other.storage_.is_null_) |
| + Init(std::move(other.value())); |
| } |
| - ~Optional() { |
| - if (has_value_) |
| - value_.~T(); |
| + Optional(const T& value) { Init(value); } |
| + |
| + Optional(T&& value) { Init(std::move(value)); } |
| + |
| + template <class... Args> |
| + explicit Optional(rtc::in_place_t, Args&&... args) { |
| + emplace(std::forward<Args>(args)...); |
| } |
| - // Copy assignment. Uses T's copy assignment if both sides have a value, T's |
| - // copy constructor if only the right-hand side has a value. |
| - Optional& operator=(const Optional& m) { |
| - if (m.has_value_) { |
| - if (has_value_) { |
| - value_ = m.value_; // T's copy assignment. |
| - } else { |
| - new (&value_) T(m.value_); // T's copy constructor. |
| - has_value_ = true; |
| - } |
| - } else if (has_value_) { |
| - value_.~T(); |
| - has_value_ = false; |
| - } |
| + ~Optional() = default; |
| + |
| + Optional& operator=(rtc::nullopt_t) { |
| + FreeIfNeeded(); |
| return *this; |
| } |
| - // Move assignment. Uses T's move assignment if both sides have a value, T's |
| - // move constructor if only the right-hand side has a value. The state of m |
| - // after it's been moved from is as for the move constructor. |
| - Optional& operator=(Optional&& m) { |
| - if (m.has_value_) { |
| - if (has_value_) { |
| - value_ = std::move(m.value_); // T's move assignment. |
| - } else { |
| - new (&value_) T(std::move(m.value_)); // T's move constructor. |
| - has_value_ = true; |
| - } |
| - } else if (has_value_) { |
| - value_.~T(); |
| - has_value_ = false; |
| + Optional& operator=(const Optional& other) { |
| + if (other.storage_.is_null_) { |
| + FreeIfNeeded(); |
| + return *this; |
| } |
| + |
| + InitOrAssign(other.value()); |
| return *this; |
| } |
| - // Swap the values if both m1 and m2 have values; move the value if only one |
| - // of them has one. |
| - friend void swap(Optional& m1, Optional& m2) { |
| - if (m1.has_value_) { |
| - if (m2.has_value_) { |
| - // Both have values: swap. |
| - using std::swap; |
| - swap(m1.value_, m2.value_); |
| - } else { |
| - // Only m1 has a value: move it to m2. |
| - new (&m2.value_) T(std::move(m1.value_)); |
| - m1.value_.~T(); // Destroy the moved-from value. |
| - m1.has_value_ = false; |
| - m2.has_value_ = true; |
| - } |
| - } else if (m2.has_value_) { |
| - // Only m2 has a value: move it to m1. |
| - new (&m1.value_) T(std::move(m2.value_)); |
| - m2.value_.~T(); // Destroy the moved-from value. |
| - m1.has_value_ = true; |
| - m2.has_value_ = false; |
| + Optional& operator=(Optional&& other) { |
| + if (other.storage_.is_null_) { |
| + FreeIfNeeded(); |
| + return *this; |
| } |
| + |
| + InitOrAssign(std::move(other.value())); |
| + return *this; |
| } |
| - // Conversion to bool to test if we have a value. |
| - explicit operator bool() const { return has_value_; } |
| + template <class U> |
| + typename std::enable_if<std::is_same<std::decay<U>, T>::value, |
| + Optional&>::type |
| + operator=(U&& value) { |
| + InitOrAssign(std::forward<U>(value)); |
| + return *this; |
| + } |
| - // Dereferencing. Only allowed if we have a value. |
| + // TODO(mlamouri): can't use 'constexpr' with RTC_DCHECK. |
| const T* operator->() const { |
| - RTC_DCHECK(has_value_); |
| - return &value_; |
| + RTC_DCHECK(!storage_.is_null_); |
| + return &value(); |
| } |
| + |
| + // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was |
| + // meant to be 'constexpr const'. |
| T* operator->() { |
| - RTC_DCHECK(has_value_); |
| - return &value_; |
| + RTC_DCHECK(!storage_.is_null_); |
| + return &value(); |
| + } |
| + |
| + constexpr const T& operator*() const& { return value(); } |
| + |
| + // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was |
| + // meant to be 'constexpr const'. |
| + T& operator*() & { return value(); } |
| + |
| + constexpr const T&& operator*() const&& { return std::move(value()); } |
| + |
| + // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was |
| + // meant to be 'constexpr const'. |
| + T&& operator*() && { return std::move(value()); } |
| + |
| + constexpr explicit operator bool() const { return !storage_.is_null_; } |
| + |
| + // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was |
| + // meant to be 'constexpr const'. |
| + T& value() & { |
| + RTC_DCHECK(!storage_.is_null_); |
| + return storage_.value_; |
| + } |
| + |
| + // TODO(mlamouri): can't use 'constexpr' with RTC_DCHECK. |
| + const T& value() const& { |
| + RTC_DCHECK(!storage_.is_null_); |
| + return storage_.value_; |
| + } |
| + |
| + // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was |
| + // meant to be 'constexpr const'. |
| + T&& value() && { |
| + RTC_DCHECK(!storage_.is_null_); |
| + return std::move(storage_.value_); |
| } |
| - const T& operator*() const { |
| - RTC_DCHECK(has_value_); |
| - return value_; |
| + |
| + // TODO(mlamouri): can't use 'constexpr' with RTC_DCHECK. |
| + const T&& value() const&& { |
| + RTC_DCHECK(!storage_.is_null_); |
| + return std::move(storage_.value_); |
| } |
| - T& operator*() { |
| - RTC_DCHECK(has_value_); |
| - return value_; |
| + |
| + template <class U> |
| + constexpr T value_or(U&& default_value) const& { |
| + // TODO(mlamouri): add the following assert when possible: |
| + // static_assert(std::is_copy_constructible<T>::value, |
| + // "T must be copy constructible"); |
| + static_assert(std::is_convertible<U, T>::value, |
| + "U must be convertible to T"); |
| + return storage_.is_null_ ? static_cast<T>(std::forward<U>(default_value)) |
| + : value(); |
| } |
| - // Dereference with a default value in case we don't have a value. |
| - const T& value_or(const T& default_val) const { |
| - return has_value_ ? value_ : default_val; |
| + template <class U> |
| + T value_or(U&& default_value) && { |
| + // TODO(mlamouri): add the following assert when possible: |
| + // static_assert(std::is_move_constructible<T>::value, |
| + // "T must be move constructible"); |
| + static_assert(std::is_convertible<U, T>::value, |
| + "U must be convertible to T"); |
| + return storage_.is_null_ ? static_cast<T>(std::forward<U>(default_value)) |
| + : std::move(value()); |
| } |
| - // Equality tests. Two Optionals are equal if they contain equivalent values, |
| - // or |
| - // if they're both empty. |
| - friend bool operator==(const Optional& m1, const Optional& m2) { |
| - return m1.has_value_ && m2.has_value_ ? m1.value_ == m2.value_ |
| - : m1.has_value_ == m2.has_value_; |
| + void swap(Optional& other) { |
| + if (storage_.is_null_ && other.storage_.is_null_) |
| + return; |
| + |
| + if (storage_.is_null_ != other.storage_.is_null_) { |
| + if (storage_.is_null_) { |
| + Init(std::move(other.storage_.value_)); |
| + other.FreeIfNeeded(); |
| + } else { |
| + other.Init(std::move(storage_.value_)); |
| + FreeIfNeeded(); |
| + } |
| + return; |
| + } |
| + |
| + RTC_DCHECK(!storage_.is_null_ && !other.storage_.is_null_); |
| + using std::swap; |
| + swap(**this, *other); |
| } |
| - friend bool operator!=(const Optional& m1, const Optional& m2) { |
| - return m1.has_value_ && m2.has_value_ ? m1.value_ != m2.value_ |
| - : m1.has_value_ != m2.has_value_; |
| + |
| + template <class... Args> |
| + void emplace(Args&&... args) { |
| + FreeIfNeeded(); |
| + Init(std::forward<Args>(args)...); |
| } |
| private: |
| - bool has_value_; // True iff value_ contains a live value. |
| - union { |
| - // By placing value_ in a union, we get to manage its construction and |
| - // destruction manually: the Optional constructors won't automatically |
| - // construct it, and the Optional destructor won't automatically destroy |
| - // it. Basically, this just allocates a properly sized and aligned block of |
| - // memory in which we can manually put a T with placement new. |
| - T value_; |
| - }; |
| + void Init(const T& value) { |
| + RTC_DCHECK(storage_.is_null_); |
| + new (&storage_.value_) T(value); |
| + storage_.is_null_ = false; |
| + } |
| + |
| + void Init(T&& value) { |
| + RTC_DCHECK(storage_.is_null_); |
| + new (&storage_.value_) T(std::move(value)); |
| + storage_.is_null_ = false; |
| + } |
| + |
| + template <class... Args> |
| + void Init(Args&&... args) { |
| + RTC_DCHECK(storage_.is_null_); |
| + new (&storage_.value_) T(std::forward<Args>(args)...); |
| + storage_.is_null_ = false; |
| + } |
| + |
| + void InitOrAssign(const T& value) { |
| + if (storage_.is_null_) |
| + Init(value); |
| + else |
| + storage_.value_ = value; |
| + } |
| + |
| + void InitOrAssign(T&& value) { |
| + if (storage_.is_null_) |
| + Init(std::move(value)); |
| + else |
| + storage_.value_ = std::move(value); |
| + } |
| + |
| + void FreeIfNeeded() { |
| + if (storage_.is_null_) |
| + return; |
| + storage_.value_.~T(); |
| + storage_.is_null_ = true; |
| + } |
| + |
| + internal::OptionalStorage<T> storage_; |
| }; |
| +template <class T> |
| +constexpr bool operator==(const Optional<T>& lhs, const Optional<T>& rhs) { |
| + return !!lhs != !!rhs ? false : lhs == nullopt || (*lhs == *rhs); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator!=(const Optional<T>& lhs, const Optional<T>& rhs) { |
| + return !(lhs == rhs); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<(const Optional<T>& lhs, const Optional<T>& rhs) { |
| + return rhs == nullopt ? false : (lhs == nullopt ? true : *lhs < *rhs); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<=(const Optional<T>& lhs, const Optional<T>& rhs) { |
| + return !(rhs < lhs); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>(const Optional<T>& lhs, const Optional<T>& rhs) { |
| + return rhs < lhs; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>=(const Optional<T>& lhs, const Optional<T>& rhs) { |
| + return !(lhs < rhs); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator==(const Optional<T>& opt, rtc::nullopt_t) { |
| + return !opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator==(rtc::nullopt_t, const Optional<T>& opt) { |
| + return !opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator!=(const Optional<T>& opt, rtc::nullopt_t) { |
| + return !!opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator!=(rtc::nullopt_t, const Optional<T>& opt) { |
| + return !!opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<(const Optional<T>& opt, rtc::nullopt_t) { |
| + return false; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<(rtc::nullopt_t, const Optional<T>& opt) { |
| + return !!opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<=(const Optional<T>& opt, rtc::nullopt_t) { |
| + return !opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<=(rtc::nullopt_t, const Optional<T>& opt) { |
| + return true; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>(const Optional<T>& opt, rtc::nullopt_t) { |
| + return !!opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>(rtc::nullopt_t, const Optional<T>& opt) { |
| + return false; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>=(const Optional<T>& opt, rtc::nullopt_t) { |
| + return true; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>=(rtc::nullopt_t, const Optional<T>& opt) { |
| + return !opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator==(const Optional<T>& opt, const T& value) { |
| + return opt != nullopt ? *opt == value : false; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator==(const T& value, const Optional<T>& opt) { |
| + return opt == value; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator!=(const Optional<T>& opt, const T& value) { |
| + return !(opt == value); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator!=(const T& value, const Optional<T>& opt) { |
| + return !(opt == value); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<(const Optional<T>& opt, const T& value) { |
| + return opt != nullopt ? *opt < value : true; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<(const T& value, const Optional<T>& opt) { |
| + return opt != nullopt ? value < *opt : false; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<=(const Optional<T>& opt, const T& value) { |
| + return !(opt > value); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator<=(const T& value, const Optional<T>& opt) { |
| + return !(value > opt); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>(const Optional<T>& opt, const T& value) { |
| + return value < opt; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>(const T& value, const Optional<T>& opt) { |
| + return opt < value; |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>=(const Optional<T>& opt, const T& value) { |
| + return !(opt < value); |
| +} |
| + |
| +template <class T> |
| +constexpr bool operator>=(const T& value, const Optional<T>& opt) { |
| + return !(value < opt); |
| +} |
| + |
| +template <class T> |
| +constexpr Optional<typename std::decay<T>::type> make_optional(T&& value) { |
| + return Optional<typename std::decay<T>::type>(std::forward<T>(value)); |
| +} |
| + |
| +template <class T> |
| +void swap(Optional<T>& lhs, Optional<T>& rhs) { |
| + lhs.swap(rhs); |
| +} |
| + |
| } // namespace rtc |
| +namespace std { |
| + |
| +template <class T> |
| +struct hash<rtc::Optional<T>> { |
| + size_t operator()(const rtc::Optional<T>& opt) const { |
| + return opt == rtc::nullopt ? 0 : std::hash<T>()(*opt); |
| + } |
| +}; |
| + |
| +} // namespace std |
| + |
| #endif // WEBRTC_BASE_OPTIONAL_H_ |