• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_TYPES_OPTIONAL_REF_H_
6 #define BASE_TYPES_OPTIONAL_REF_H_
7 
8 #include <memory>
9 #include <optional>
10 #include <type_traits>
11 
12 #include "base/check.h"
13 #include "base/memory/raw_ptr.h"
14 #include "third_party/abseil-cpp/absl/base/attributes.h"
15 #include "third_party/abseil-cpp/absl/types/optional.h"
16 
17 namespace base {
18 
19 // `optional_ref<T>` is similar to `absl::optional<T>`, except it does not own
20 // the underlying value.
21 //
22 // When passing an optional parameter, prefer `optional_ref` to `const
23 // absl::optional<T>&` as the latter often results in hidden copies due to
24 // implicit conversions, e.g. given the function:
25 //
26 //   void TakesOptionalString(const absl::optional<std::string>& str);
27 //
28 // And a call to that looks like:
29 //
30 //   std::string s = "Hello world!";
31 //   TakesOptionalString(s);
32 //
33 // This copies `s` into a temporary `absl::optional<std::string>` in order to
34 // call `TakesOptionalString()`.
35 //
36 // The C++ style guide recommends using `const T*` instead of `const
37 // absl::optional<T>&` when `T` would normally be passed by reference. However
38 // `const T*` is not always a good substitute because:
39 //
40 // - `const T*` disallows the use of temporaries, since it is not possible to
41 //   take the address of a temporary.
42 // - additional boilerplate (e.g. `OptionalToPtr`) is required to pass an
43 //   `absl::optional<T>` to a `const T*` function parameter.
44 //
45 // Like `span<T>`, mutability of `optional_ref<T>` is controlled by the template
46 // argument `T`; e.g. `optional_ref<const int>` only allows const access to the
47 // referenced `int` value.
48 //
49 // Thus, `optional_ref<const T>` can be constructed from:
50 // - `absl::nullopt`
51 // - `const T*` or `T*`
52 // - `const T&` or `T&`
53 // ` `const absl::optional<T>&` or `absl::optional<T>&`
54 //
55 // While `optional_ref<T>` can only be constructed from:
56 // - `absl::nullopt`
57 // - `T*`
58 // - `T&`
59 // - `absl::optional<T>&`
60 //
61 // Implicit conversions are disallowed, e.g. this will not compile:
62 //
63 //   [](base::optional_ref<std::string> s) {}("Hello world!");
64 //
65 // This restriction may be relaxed in the future if it proves too onerous.
66 //
67 // `optional_ref<T>` is lightweight and should be passed by value. It is copy
68 // constructible but not copy assignable, to reduce the risk of lifetime bugs.
69 template <typename T>
70 class optional_ref {
71  private:
72   // Disallowed because `absl::optional` (and `std::optional`) do not allow
73   // their template argument to be a reference type.
74   static_assert(!std::is_reference_v<T>,
75                 "T must not be a reference type (use a pointer?)");
76 
77   // Both checks are important here, as:
78   // - optional_ref does not allow silent implicit conversions between types,
79   //   so the decayed types must match exactly.
80   // - unless the types differ only in const qualification, and T is at least as
81   //   const-qualified as the incoming type U.
82   template <typename U>
83   static constexpr bool IsCompatibleV =
84       std::is_same_v<std::decay_t<T>, std::decay_t<U>> &&
85       std::is_convertible_v<U*, T*>;
86 
87  public:
88   // Constructs an empty `optional_ref`.
89   constexpr optional_ref() = default;
90   // NOLINTNEXTLINE(google-explicit-constructor)
optional_ref(absl::nullopt_t)91   constexpr optional_ref(absl::nullopt_t) {}
92 
93   // Constructs an `optional_ref` from an `absl::optional`; the resulting
94   // `optional_ref` is empty iff `o` is empty.
95   //
96   // Note: when constructing from a const reference, `optional_ref`'s template
97   // argument must be const-qualified as well.
98   // Note 2: avoiding direct use of `T` prevents implicit conversions.
99   template <typename U>
requires(std::is_const_v<T> && IsCompatibleV<U>)100     requires(std::is_const_v<T> && IsCompatibleV<U>)
101   // NOLINTNEXTLINE(google-explicit-constructor)
102   constexpr optional_ref(
103       const absl::optional<U>& o ABSL_ATTRIBUTE_LIFETIME_BOUND)
104       : ptr_(o ? &*o : nullptr) {}
105   template <typename U>
requires(IsCompatibleV<U>)106     requires(IsCompatibleV<U>)
107   // NOLINTNEXTLINE(google-explicit-constructor)
108   constexpr optional_ref(absl::optional<U>& o ABSL_ATTRIBUTE_LIFETIME_BOUND)
109       : ptr_(o ? &*o : nullptr) {}
110 
111   // Constructs an `optional_ref` from a pointer; the resulting `optional_ref`
112   // is empty iff `p` is null.
113   //
114   // Note: when constructing from a const pointer, `optional_ref`'s template
115   // argument must be const-qualified as well.
116   // Note 2: avoiding direct use of `T` prevents implicit conversions.
117   template <typename U>
requires(IsCompatibleV<U>)118     requires(IsCompatibleV<U>)
119   // NOLINTNEXTLINE(google-explicit-constructor)
120   constexpr optional_ref(U* p ABSL_ATTRIBUTE_LIFETIME_BOUND) : ptr_(p) {}
121 
122   // Constructs an `optional_ref` from a reference; the resulting `optional_ref`
123   // is never empty.
124   //
125   // Note: when constructing from a const reference, `optional_ref`'s template
126   // argument must be const-qualified as well.
127   // Note 2: avoiding direct use of `T` prevents implicit conversions.
128   template <typename U>
requires(IsCompatibleV<const U>)129     requires(IsCompatibleV<const U>)
130   // NOLINTNEXTLINE(google-explicit-constructor)
131   constexpr optional_ref(const U& r ABSL_ATTRIBUTE_LIFETIME_BOUND)
132       : ptr_(std::addressof(r)) {}
133   template <typename U>
requires(IsCompatibleV<U>)134     requires(IsCompatibleV<U>)
135   // NOLINTNEXTLINE(google-explicit-constructor)
136   constexpr optional_ref(U& r ABSL_ATTRIBUTE_LIFETIME_BOUND)
137       : ptr_(std::addressof(r)) {}
138 
139   // An empty `optional_ref` must be constructed with `absl::nullopt`, not
140   // `nullptr`. Otherwise, `optional_ref<T*>` constructed with `nullptr` would
141   // be ambiguous: is it empty or is it engaged with a value of `nullptr`?
142   constexpr optional_ref(std::nullptr_t) = delete;
143 
144   // Constructs a `optional_ref<const T>` from a `optional_ref<T>`. Conversions
145   // in the reverse direction are disallowed.
146   // NOLINTNEXTLINE(google-explicit-constructor)
optional_ref(optional_ref<std::remove_const_t<T>> rhs)147   constexpr optional_ref(optional_ref<std::remove_const_t<T>> rhs)
148     requires(std::is_const_v<T>)
149       : ptr_(rhs.as_ptr()) {}
150 
151   // Copy construction is allowed to make it possible to pass `optional_ref`s to
152   // another call. However, assignment is disallowed, as it makes it easy to
153   // violate lifetime bounds. Use `CopyAsOptional()` if an `optional_ref` needs
154   // to be persisted beyond the scope of a function call.
155   constexpr optional_ref(const optional_ref&) = default;
156   optional_ref& operator=(const optional_ref&) = delete;
157 
158   // CHECKs if the `optional_ref` is empty.
159   constexpr T* operator->() const {
160     CHECK(ptr_);
161     return ptr_;
162   }
163 
164   // CHECKs if the `optional_ref` is empty.
165   constexpr T& operator*() const {
166     CHECK(ptr_);
167     return *ptr_;
168   }
169 
170   // Returns `true` iff the `optional_ref` is non-empty.
has_value()171   constexpr bool has_value() const { return ptr_; }
172 
173   // CHECKs if the `optional_ref` is empty.
value()174   constexpr T& value() const {
175     CHECK(ptr_);
176     return *ptr_;
177   }
178 
179   // Convenience method for turning an `optional_ref` into a pointer.
as_ptr()180   constexpr T* as_ptr() const { return ptr_; }
181 
182   // Convenience method for turning a non-owning `optional_ref` into an owning
183   // `absl::optional`. Incurs a copy; useful when saving an `optional_ref`
184   // function parameter as a field, et cetera.
185   template <typename U = std::decay_t<T>>
requires(std::constructible_from<U,T>)186     requires(std::constructible_from<U, T>)
187   constexpr absl::optional<U> CopyAsOptional() const {
188     return ptr_ ? absl::optional<U>(*ptr_) : absl::nullopt;
189   }
190 
191  private:
192   raw_ptr<T> const ptr_ = nullptr;
193 };
194 
195 template <typename T>
196 optional_ref(const T&) -> optional_ref<const T>;
197 template <typename T>
198 optional_ref(T&) -> optional_ref<T>;
199 
200 template <typename T>
201 optional_ref(const absl::optional<T>&) -> optional_ref<const T>;
202 template <typename T>
203 optional_ref(absl::optional<T>&) -> optional_ref<T>;
204 
205 template <typename T>
206 optional_ref(T*) -> optional_ref<T>;
207 
208 template <typename T>
209 constexpr bool operator==(std::nullopt_t, optional_ref<T> x) {
210   return !x.has_value();
211 }
212 
213 template <typename T>
214 constexpr bool operator==(optional_ref<T> x, std::nullopt_t) {
215   return !x.has_value();
216 }
217 
218 }  // namespace base
219 
220 #endif  // BASE_TYPES_OPTIONAL_REF_H_
221