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