Base: add Optional<T>.
This is an implementation of the C++17 std::optional<> feature: http://en.cppreference.com/w/cpp/utility/optional Chromium documentation: https://chromium.googlesource.com/chromium/src/+/master/docs/optional.md BUG=521269 Committed: https://crrev.com/8756e4f20df53bbb7fbb49b2ca19fda93a95bdb4 Cr-Commit-Position: refs/heads/master@{#388187} Review URL: https://codereview.chromium.org/1245163002 Cr-Commit-Position: refs/heads/master@{#388232}
This commit is contained in:
@ -559,6 +559,7 @@ component("base") {
|
||||
"numerics/safe_math_impl.h",
|
||||
"observer_list.h",
|
||||
"observer_list_threadsafe.h",
|
||||
"optional.h",
|
||||
"os_compat_android.cc",
|
||||
"os_compat_android.h",
|
||||
"os_compat_nacl.cc",
|
||||
@ -1792,6 +1793,7 @@ test("base_unittests") {
|
||||
"native_library_unittest.cc",
|
||||
"numerics/safe_numerics_unittest.cc",
|
||||
"observer_list_unittest.cc",
|
||||
"optional_unittest.cc",
|
||||
"os_compat_android_unittest.cc",
|
||||
"path_service_unittest.cc",
|
||||
"pickle_unittest.cc",
|
||||
|
@ -496,6 +496,7 @@
|
||||
'native_library_unittest.cc',
|
||||
'numerics/safe_numerics_unittest.cc',
|
||||
'observer_list_unittest.cc',
|
||||
'optional_unittest.cc',
|
||||
'os_compat_android_unittest.cc',
|
||||
'path_service_unittest.cc',
|
||||
'pickle_unittest.cc',
|
||||
|
@ -446,6 +446,7 @@
|
||||
'numerics/safe_math_impl.h',
|
||||
'observer_list.h',
|
||||
'observer_list_threadsafe.h',
|
||||
'optional.h',
|
||||
'os_compat_android.cc',
|
||||
'os_compat_android.h',
|
||||
'os_compat_nacl.cc',
|
||||
|
427
base/optional.h
Normal file
427
base/optional.h
Normal file
@ -0,0 +1,427 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_OPTIONAL_H_
|
||||
#define BASE_OPTIONAL_H_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/aligned_memory.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// 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);
|
||||
|
||||
// base::Optional is a Chromium 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
|
||||
//
|
||||
// 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 Chromium.
|
||||
// - 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 Chromium.
|
||||
// - All the non-members are in the 'base' namespace instead of 'std'.
|
||||
template <typename T>
|
||||
class Optional {
|
||||
public:
|
||||
constexpr Optional() = default;
|
||||
Optional(base::nullopt_t) : Optional() {}
|
||||
|
||||
Optional(const Optional& other) {
|
||||
if (!other.is_null_)
|
||||
Init(other.value());
|
||||
}
|
||||
|
||||
Optional(Optional&& other) {
|
||||
if (!other.is_null_)
|
||||
Init(std::move(other.value()));
|
||||
}
|
||||
|
||||
Optional(const T& value) { Init(value); }
|
||||
|
||||
Optional(T&& value) { Init(std::move(value)); }
|
||||
|
||||
template <class... Args>
|
||||
explicit Optional(base::in_place_t, Args&&... args) {
|
||||
emplace(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
~Optional() {
|
||||
// TODO(mlamouri): use is_trivially_destructible<T>::value when possible.
|
||||
FreeIfNeeded();
|
||||
}
|
||||
|
||||
Optional& operator=(base::nullopt_t) {
|
||||
FreeIfNeeded();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Optional& operator=(const Optional& other) {
|
||||
if (other.is_null_) {
|
||||
FreeIfNeeded();
|
||||
return *this;
|
||||
}
|
||||
|
||||
InitOrAssign(other.value());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Optional& operator=(Optional&& other) {
|
||||
if (other.is_null_) {
|
||||
FreeIfNeeded();
|
||||
return *this;
|
||||
}
|
||||
|
||||
InitOrAssign(std::move(other.value()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO(mlamouri): can't use 'constexpr' with DCHECK.
|
||||
const T* operator->() const {
|
||||
DCHECK(!is_null_);
|
||||
return &value();
|
||||
}
|
||||
|
||||
// TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
|
||||
// meant to be 'constexpr const'.
|
||||
T* operator->() {
|
||||
DCHECK(!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 !is_null_; }
|
||||
|
||||
// TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
|
||||
// meant to be 'constexpr const'.
|
||||
T& value() & {
|
||||
DCHECK(!is_null_);
|
||||
return *buffer_.template data_as<T>();
|
||||
}
|
||||
|
||||
// TODO(mlamouri): can't use 'constexpr' with DCHECK.
|
||||
const T& value() const& {
|
||||
DCHECK(!is_null_);
|
||||
return *buffer_.template data_as<T>();
|
||||
}
|
||||
|
||||
// TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
|
||||
// meant to be 'constexpr const'.
|
||||
T&& value() && {
|
||||
DCHECK(!is_null_);
|
||||
return std::move(*buffer_.template data_as<T>());
|
||||
}
|
||||
|
||||
// TODO(mlamouri): can't use 'constexpr' with DCHECK.
|
||||
const T&& value() const&& {
|
||||
DCHECK(!is_null_);
|
||||
return std::move(*buffer_.template data_as<T>());
|
||||
}
|
||||
|
||||
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 is_null_ ? static_cast<T>(std::forward<U>(default_value)) : value();
|
||||
}
|
||||
|
||||
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 is_null_ ? static_cast<T>(std::forward<U>(default_value))
|
||||
: std::move(value());
|
||||
}
|
||||
|
||||
void swap(Optional& other) {
|
||||
if (is_null_ && other.is_null_)
|
||||
return;
|
||||
|
||||
if (is_null_ != other.is_null_) {
|
||||
if (is_null_) {
|
||||
Init(std::move(*other.buffer_.template data_as<T>()));
|
||||
other.FreeIfNeeded();
|
||||
} else {
|
||||
other.Init(std::move(*buffer_.template data_as<T>()));
|
||||
FreeIfNeeded();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK(!is_null_ && !other.is_null_);
|
||||
using std::swap;
|
||||
swap(**this, *other);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void emplace(Args&&... args) {
|
||||
FreeIfNeeded();
|
||||
Init(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
void Init(const T& value) {
|
||||
DCHECK(is_null_);
|
||||
new (buffer_.template data_as<T>()) T(value);
|
||||
is_null_ = false;
|
||||
}
|
||||
|
||||
void Init(T&& value) {
|
||||
DCHECK(is_null_);
|
||||
new (buffer_.template data_as<T>()) T(std::move(value));
|
||||
is_null_ = false;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void Init(Args&&... args) {
|
||||
DCHECK(is_null_);
|
||||
new (buffer_.template data_as<T>()) T(std::forward<Args>(args)...);
|
||||
is_null_ = false;
|
||||
}
|
||||
|
||||
void InitOrAssign(const T& value) {
|
||||
if (is_null_)
|
||||
Init(value);
|
||||
else
|
||||
*buffer_.template data_as<T>() = value;
|
||||
}
|
||||
|
||||
void InitOrAssign(T&& value) {
|
||||
if (is_null_)
|
||||
Init(std::move(value));
|
||||
else
|
||||
*buffer_.template data_as<T>() = std::move(value);
|
||||
}
|
||||
|
||||
void FreeIfNeeded() {
|
||||
if (is_null_)
|
||||
return;
|
||||
buffer_.template data_as<T>()->~T();
|
||||
is_null_ = true;
|
||||
}
|
||||
|
||||
bool is_null_ = true;
|
||||
base::AlignedMemory<sizeof(T), ALIGNOF(T)> buffer_;
|
||||
};
|
||||
|
||||
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, base::nullopt_t) {
|
||||
return !opt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator==(base::nullopt_t, const Optional<T>& opt) {
|
||||
return !opt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator!=(const Optional<T>& opt, base::nullopt_t) {
|
||||
return !!opt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator!=(base::nullopt_t, const Optional<T>& opt) {
|
||||
return !!opt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator<(const Optional<T>& opt, base::nullopt_t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator<(base::nullopt_t, const Optional<T>& opt) {
|
||||
return !!opt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator<=(const Optional<T>& opt, base::nullopt_t) {
|
||||
return !opt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator<=(base::nullopt_t, const Optional<T>& opt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator>(const Optional<T>& opt, base::nullopt_t) {
|
||||
return !!opt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator>(base::nullopt_t, const Optional<T>& opt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator>=(const Optional<T>& opt, base::nullopt_t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr bool operator>=(base::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 base
|
||||
|
||||
namespace std {
|
||||
|
||||
template <class T>
|
||||
struct hash<base::Optional<T>> {
|
||||
size_t operator()(const base::Optional<T>& opt) const {
|
||||
return opt == base::nullopt ? 0 : std::hash<T>()(*opt);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // BASE_OPTIONAL_H_
|
1224
base/optional_unittest.cc
Normal file
1224
base/optional_unittest.cc
Normal file
File diff suppressed because it is too large
Load Diff
108
docs/optional.md
Normal file
108
docs/optional.md
Normal file
@ -0,0 +1,108 @@
|
||||
# base::Optional
|
||||
|
||||
`base::Optional<T>` is a container that might contain an instance of `T`.
|
||||
|
||||
[TOC]
|
||||
|
||||
## History
|
||||
|
||||
[base::Optional<T>](https://code.google.com/p/chromium/codesearch#chromium/src/base/optional.h)
|
||||
is an implementation of [std::optional<T>](http://en.cppreference.com/w/cpp/utility/optional),
|
||||
initially a C++ experimental feature and now part of the C++17 standard. The
|
||||
Chromium's implementation is as close as possible to the specification. The
|
||||
differences are listed at the beginning of the header. The most important
|
||||
difference is that all the objects and types are part of the `base::` namespace
|
||||
instead of `std::`. Also, following Chromium coding style, the class is named
|
||||
`Optional` instead of `optional`.
|
||||
|
||||
## API description
|
||||
|
||||
For a deep API description, please have a look at [std::optional<T>](http://en.cppreference.com/w/cpp/utility/optional)
|
||||
or the [Chromium implementation](https://code.google.com/p/chromium/codesearch#chromium/src/base/optional.h).
|
||||
|
||||
When initialized without a value, `base::Optional<T>` will be empty. When empty,
|
||||
the `operator bool` will return `false` and `value()` should not be called. An
|
||||
empty `base::Optional<T>` is equal to `base::nullopt_t`.
|
||||
|
||||
```C++
|
||||
base::Optional<int> opt;
|
||||
opt == true; // false
|
||||
opt.value(); // illegal, will DCHECK
|
||||
opt == base::nullopt_t; // true
|
||||
```
|
||||
|
||||
To avoid calling `value()` when an `base::Optional<T>` is empty, instead of
|
||||
doing checks, it is possible to use `value_or()` and pass a default value:
|
||||
|
||||
```C++
|
||||
base::Optional<int> opt;
|
||||
opt.value_or(42); // will return 42
|
||||
```
|
||||
|
||||
It is possible to initialize a `base::Optional<T>` from its constructor and
|
||||
`operator=` using `T` or another `base::Optional<T>`:
|
||||
|
||||
```C++
|
||||
base::Optional<int> opt_1 = 1; // .value() == 1
|
||||
base::Optional<int> opt_2 = base::Optional<int>(2); // .value() == 2
|
||||
```
|
||||
|
||||
All basic operators should be available on `base::Optional<T>`: it is possible
|
||||
to compare a `base::Optional<T>` with another or with a `T` or
|
||||
`base::nullopt_t`.
|
||||
|
||||
```C++
|
||||
base::Optional<int> opt_1;
|
||||
base::Optional<int> opt_2 = 2;
|
||||
|
||||
opt_1 == opt_2; // false
|
||||
opt_1 = 1;
|
||||
|
||||
opt_1 <= opt_2; // true
|
||||
opt_1 == 1; // true
|
||||
opt_1 == base::nullopt_t; // false
|
||||
```
|
||||
|
||||
`base::Optional<T>` has a helper function `make_optional<T&&>`:
|
||||
|
||||
```C++
|
||||
base::Optional<int> opt = make_optional<int>(GetMagicNumber());
|
||||
```
|
||||
|
||||
Finally, `base::Optional<T>` is integrated with `std::hash`, using
|
||||
`std::hash<T>` if it is not empty, a default value otherwise. `.emplace()` and
|
||||
`.swap()` can be used as members functions and `std::swap()` will work with two
|
||||
`base::Optional<T>` objects.
|
||||
|
||||
## How is it implemented?
|
||||
|
||||
`base::Optional<T>` is implemented using `base::AlignedMemory`. The object
|
||||
doesn't behave like a pointer and doesn't do dynamic memory allocation. In
|
||||
other words, it is guaranteed to have an object allocated when it is not empty.
|
||||
|
||||
## When to use?
|
||||
|
||||
A very common use case is for classes and structures that have an object not
|
||||
always available, because it is early initialized or because the underlying data
|
||||
structure doesn't require it.
|
||||
|
||||
It is common to implement such patterns with dynamically allocated pointers,
|
||||
`nullptr` representing the absence of value. Other approaches involve
|
||||
`std::pair<T, bool>` where bool represents whether the object is actually
|
||||
present.
|
||||
|
||||
It can also be used for simple types, for example when a structure wants to
|
||||
represent whether the user or the underlying data structure has some value
|
||||
unspecified, a `base::Optional<int>` would be easier to understand than a
|
||||
special value representing the lack of it. For example, using -1 as the
|
||||
undefined value when the expected value can't be negative.
|
||||
|
||||
## When not to use?
|
||||
|
||||
It is recommended to not use `base::Optional<T>` as a function parameter as it
|
||||
will force the callers to use `base::Optional<T>`. Instead, it is recommended to
|
||||
keep using `T*` for arguments that can be ommited, with `nullptr` representing
|
||||
no value.
|
||||
|
||||
Furthermore, depending on `T`, MSVC might fail to compile code using
|
||||
`base::Optional<T>` as a parameter because of memory alignment issues.
|
Reference in New Issue
Block a user