0

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:
mlamouri
2016-04-19 10:27:01 -07:00
committed by Commit bot
parent e59dc80836
commit 53f6b25835
6 changed files with 1763 additions and 0 deletions

@ -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

@ -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

File diff suppressed because it is too large Load Diff

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.