Google Cloud C++ Client  1.42.0
C++ Client Library for Google Cloud Platform
options.h
Go to the documentation of this file.
1 // Copyright 2021 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPTIONS_H
16 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPTIONS_H
17 
18 #include "google/cloud/internal/type_list.h"
19 #include "google/cloud/version.h"
20 #include "absl/base/attributes.h"
21 #include "absl/memory/memory.h"
22 #include <set>
23 #include <typeindex>
24 #include <typeinfo>
25 #include <unordered_map>
26 
27 namespace google {
28 namespace cloud {
30 
31 class Options;
32 namespace internal {
33 Options MergeOptions(Options, Options);
34 void CheckExpectedOptionsImpl(std::set<std::type_index> const&, Options const&,
35  char const*);
36 template <typename T>
37 inline T const& DefaultValue() {
38  static auto const* const kDefaultValue = new T{};
39  return *kDefaultValue;
40 }
41 } // namespace internal
42 
43 /**
44  * A class that holds option structs indexed by their type.
45  *
46  * An "Option" is any struct that has a public `Type` member typedef. By
47  * convention they are named like "FooOption". Each library (e.g., spanner,
48  * storage) may define their own set of options. Additionally, there are common
49  * options defined that many libraries may use. All these options may be set in
50  * a single `Options` instance, and each library will look at the options that
51  * it needs.
52  *
53  * Here's an overview of this class's interface, but see the method
54  * documentation below for details.
55  *
56  * - `.set<T>(x)` -- Sets the option `T` to value `x`
57  * - `.has<T>()` -- Returns true iff option `T` is set
58  * - `.unset<T>()` -- Removes the option `T`
59  * - `.get<T>()` -- Gets a const-ref to the value of option `T`
60  * - `.lookup<T>(x)` -- Gets a non-const-ref to option `T`'s value, initializing
61  * it to `x` if it was not set (`x` is optional).
62  *
63  * @par Example:
64  *
65  * @code
66  * struct FooOption {
67  * using Type = int;
68  * };
69  * struct BarOption {
70  * using Type = std::set<std::string>;
71  * };
72  * ...
73  * Options opts;
74  *
75  * assert(opts.get<FooOption>() == 0);
76  * opts.set<FooOption>(42);
77  * assert(opts.get<FooOption>() == 42);
78  *
79  * // Inserts two elements directly into the BarOption's std::set.
80  * opts.lookup<BarOption>().insert("hello");
81  * opts.lookup<BarOption>().insert("world");
82  *
83  * std::set<std::string> const& bar = opts.get<BarOption>();
84  * assert(bar == std::set<std::string>{"hello", "world"});
85  * @endcode
86  */
87 class Options {
88  private:
89  template <typename T>
90  using ValueTypeT = typename T::Type;
91 
92  public:
93  /// Constructs an empty instance.
94  Options() = default;
95 
96  Options(Options const& rhs) {
97  for (auto const& kv : rhs.m_) m_.emplace(kv.first, kv.second->clone());
98  }
99  Options& operator=(Options const& rhs) {
100  Options tmp(rhs);
101  std::swap(m_, tmp.m_);
102  return *this;
103  }
104  Options(Options&&) = default;
105  Options& operator=(Options&&) = default;
106 
107  /**
108  * Sets option `T` to the value @p v and returns a reference to `*this`.
109  *
110  * @code
111  * struct FooOption {
112  * using Type = int;
113  * };
114  * auto opts = Options{}.set<FooOption>(123);
115  * @endcode
116  *
117  * @tparam T the option type
118  * @param v the value to set the option T
119  */
120  template <typename T>
121  Options& set(ValueTypeT<T> v) {
122  m_[typeid(T)] = absl::make_unique<Data<T>>(std::move(v));
123  return *this;
124  }
125 
126  /**
127  * Returns true IFF an option with type `T` exists.
128  *
129  * @tparam T the option type
130  */
131  template <typename T>
132  bool has() const {
133  return m_.find(typeid(T)) != m_.end();
134  }
135 
136  /**
137  * Erases the option specified by the type `T`.
138  *
139  * @tparam T the option type
140  */
141  template <typename T>
142  void unset() {
143  m_.erase(typeid(T));
144  }
145 
146  /**
147  * Returns a reference to the value for `T`, or a value-initialized default
148  * if `T` was not set.
149  *
150  * This method will always return a reference to a valid value of the correct
151  * type for option `T`, whether or not `T` has actually been set. Use
152  * `has<T>()` to check whether or not the option has been set.
153  *
154  * @code
155  * struct FooOption {
156  * using Type = std::set<std::string>;
157  * };
158  * Options opts;
159  * std::set<std::string> const& x = opts.get<FooOption>();
160  * assert(x.empty());
161  * assert(!x.has<FooOption>());
162  *
163  * opts.set<FooOption>({"foo"});
164  * assert(opts.get<FooOption>().size() == 1);
165  * @endcode
166  *
167  * @tparam T the option type
168  */
169  template <typename T>
170  ValueTypeT<T> const& get() const {
171  auto const it = m_.find(typeid(T));
172  if (it == m_.end()) return internal::DefaultValue<ValueTypeT<T>>();
173  auto const* value = it->second->data_address();
174  return *reinterpret_cast<ValueTypeT<T> const*>(value);
175  }
176 
177  /**
178  * Returns a reference to the value for option `T`, setting the value to @p
179  * init_value if necessary.
180  *
181  * @code
182  * struct BigOption {
183  * using Type = std::set<std::string>;
184  * };
185  * Options opts;
186  * std::set<std::string>& x = opts.lookup<BigOption>();
187  * assert(x.empty());
188  *
189  * x.insert("foo");
190  * opts.lookup<BigOption>().insert("bar");
191  * assert(x.size() == 2);
192  * @endcode
193  *
194  * @tparam T the option type
195  * @param value the initial value to use if `T` is not set (optional)
196  */
197  template <typename T>
198  ValueTypeT<T>& lookup(ValueTypeT<T> value = {}) {
199  auto p = m_.find(typeid(T));
200  if (p == m_.end()) {
201  p = m_.emplace(typeid(T), absl::make_unique<Data<T>>(std::move(value)))
202  .first;
203  }
204  auto* v = p->second->data_address();
205  return *reinterpret_cast<ValueTypeT<T>*>(v);
206  }
207 
208  private:
209  friend Options internal::MergeOptions(Options, Options);
210  friend void internal::CheckExpectedOptionsImpl(
211  std::set<std::type_index> const&, Options const&, char const*);
212 
213  // The type-erased data holder of all the option values.
214  class DataHolder {
215  public:
216  virtual ~DataHolder() = default;
217  virtual void const* data_address() const = 0;
218  virtual void* data_address() = 0;
219  virtual std::unique_ptr<DataHolder> clone() const = 0;
220  };
221 
222  // The data holder for all the option values.
223  template <typename T>
224  class Data : public DataHolder {
225  public:
226  explicit Data(ValueTypeT<T> v) : value_(std::move(v)) {}
227  ~Data() override = default;
228 
229  void const* data_address() const override { return &value_; }
230  void* data_address() override { return &value_; }
231  std::unique_ptr<DataHolder> clone() const override {
232  return absl::make_unique<Data<T>>(*this);
233  }
234 
235  private:
236  ValueTypeT<T> value_;
237  };
238 
239  // Note that (1) `typeid(T)` returns a `std::type_info const&`, but that
240  // implicitly converts to a `std::type_index`, and (2) `std::hash<>` is
241  // specialized for `std::type_index` to use `std::type_index::hash_code()`.
242  std::unordered_map<std::type_index, std::unique_ptr<DataHolder>> m_;
243 };
244 
245 /**
246  * A template to hold a list of "option" types.
247  *
248  * This can be a useful way to create meaningful lists of options. For example,
249  * there could be a list containing all the gRPC options. Or a list of all
250  * ProductX options. This gives us a way to link to lists of options with
251  * doxygen, and to do some checking about what options a function may expect.
252  */
253 template <typename... T>
254 using OptionList = internal::TypeList<T...>;
255 
256 namespace internal {
257 
258 // Wraps `T` in a `OptionList`, unless it was already one.
259 template <typename T>
260 struct WrapTypeList {
261  using Type = OptionList<T>;
262 };
263 template <typename... T>
264 struct WrapTypeList<OptionList<T...>> {
265  using Type = OptionList<T...>; // Note: Doesn't work w/ nested OptionLists.
266 };
267 template <typename T>
268 using WrapTypeListT = typename WrapTypeList<T>::Type;
269 
270 template <typename... T>
271 void CheckExpectedOptionsImpl(OptionList<T...> const&, Options const& opts,
272  char const* caller) {
273  CheckExpectedOptionsImpl({typeid(T)...}, opts, caller);
274 }
275 
276 /**
277  * Checks that `Options` only contains the given expected options or a subset
278  * of them.
279  *
280  * Logs all unexpected options. Note that logging is not always shown
281  * on the console. Set the environment variable
282  * `GOOGLE_CLOUD_CPP_ENABLE_CLOG=yes` to enable logging.
283  *
284  * Options may be specified directly or as a collection in an `OptionList`.
285  * For example,
286  *
287  * @code
288  * struct FooOption { using Type = int; };
289  * struct BarOption { using Type = int; };
290  * using MyOptions = OptionList<FooOption, BarOption>;
291  *
292  * struct BazOption { using Type = int; };
293  *
294  * // All valid ways to call this with varying expectations.
295  * CheckExpectedOptions<FooOption>(opts, "test caller");
296  * CheckExpectedOptions<FooOption, BarOption>(opts, "test caller");
297  * CheckExpectedOptions<MyOptions>(opts, "test caller");
298  * CheckExpectedOptions<BazOption, MyOptions>(opts, "test caller");
299  * @endcode
300  *
301  * @param opts the `Options` to check.
302  * @param caller some string indicating the callee function; logged IFF there's
303  * an unexpected option
304  */
305 template <typename... T>
306 void CheckExpectedOptions(Options const& opts, char const* caller) {
307  using ExpectedTypes = TypeListCatT<WrapTypeListT<T>...>;
308  CheckExpectedOptionsImpl(ExpectedTypes{}, opts, caller);
309 }
310 
311 /**
312  * Moves the options from @p alternatives into @p preferred and returns the
313  * result. If an option already exists in @p preferred its value is used instead
314  * of the values in @p alternatives.
315  */
316 Options MergeOptions(Options preferred, Options alternatives);
317 
318 /**
319  * The prevailing options for the current operation.
320  */
321 Options const& CurrentOptions();
322 
323 /**
324  * RAII object to set/restore the prevailing options for the enclosing scope.
325  *
326  * @code
327  * struct IntOption { using Type = int; };
328  * assert(!internal::CurrentOptions().has<IntOption>());
329  * {
330  * internal::OptionsSpan span(Options{}.set<IntOption>(1));
331  * assert(internal::CurrentOptions().get<IntOption>() == 1);
332  * {
333  * internal::OptionsSpan span(Options{}.set<IntOption>(2));
334  * assert(internal::CurrentOptions().get<IntOption>() == 2);
335  * }
336  * assert(internal::CurrentOptions().get<IntOption>() == 1);
337  * }
338  * assert(!internal::CurrentOptions().has<IntOption>());
339  * @endcode
340  *
341  * @param opts the `Options` to install.
342  */
343 class ABSL_MUST_USE_RESULT OptionsSpan {
344  public:
345  explicit OptionsSpan(Options opts);
346 
347  // `OptionsSpan` should not be copied/moved.
348  OptionsSpan(OptionsSpan const&) = delete;
349  OptionsSpan(OptionsSpan&&) = delete;
350  OptionsSpan& operator=(OptionsSpan const&) = delete;
351  OptionsSpan& operator=(OptionsSpan&&) = delete;
352 
353  // `OptionsSpan` should only be used for block-scoped objects.
354  static void* operator new(std::size_t) = delete;
355  static void* operator new[](std::size_t) = delete;
356 
357  ~OptionsSpan();
358 
359  private:
360  Options opts_;
361 };
362 
363 } // namespace internal
364 
366 } // namespace cloud
367 } // namespace google
368 
369 #endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPTIONS_H