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