Google Cloud C++ Client  1.32.1
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 // http://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/types/any.h"
21 #include <set>
22 #include <typeindex>
23 #include <typeinfo>
24 #include <unordered_map>
25 
26 namespace google {
27 namespace cloud {
28 inline namespace GOOGLE_CLOUD_CPP_NS {
29 
30 class Options;
31 namespace internal {
32 Options MergeOptions(Options, Options);
33 void CheckExpectedOptionsImpl(std::set<std::type_index> const&, Options const&,
34  char const*);
35 } // namespace internal
36 
37 /**
38  * A class that holds option structs indexed by their type.
39  *
40  * An "Option" is any struct that has a public `Type` member typedef. By
41  * convention they are named like "FooOption". Each library (e.g., spanner,
42  * storage) may define their own set of options. Additionally, there are common
43  * options defined that many libraries may use. All these options may be set in
44  * a single `Options` instance, and each library will look at the options that
45  * it needs.
46  *
47  * Here's an overview of this class's interface, but see the method
48  * documentation below for details.
49  *
50  * - `.set<T>(x)` -- Sets the option `T` to value `x`
51  * - `.has<T>()` -- Returns true iff option `T` is set
52  * - `.unset<T>()` -- Removes the option `T`
53  * - `.get<T>()` -- Gets a const-ref to the value of option `T`
54  * - `.lookup<T>(x)` -- Gets a non-const-ref to option `T`'s value, initializing
55  * it to `x` if it was not set (`x` is optional).
56  *
57  * @par Example:
58  *
59  * @code
60  * struct FooOption {
61  * using Type = int;
62  * };
63  * struct BarOption {
64  * using Type = std::set<std::string>;
65  * };
66  * ...
67  * Options opts;
68  *
69  * assert(opts.get<FooOption>() == 0);
70  * opts.set<FooOption>(42);
71  * assert(opts.get<FooOption>() == 42);
72  *
73  * // Inserts two elements directly into the BarOption's std::set.
74  * opts.lookup<BarOption>().insert("hello");
75  * opts.lookup<BarOption>().insert("world");
76  *
77  * std::set<std::string> const& bar = opts.get<BarOption>();
78  * assert(bar == std::set<std::string>{"hello", "world"});
79  * @endcode
80  */
81 class Options {
82  private:
83  template <typename T>
84  using ValueTypeT = typename T::Type;
85 
86  public:
87  /// Constructs an empty instance.
88  Options() = default;
89 
90  Options(Options const&) = default;
91  Options& operator=(Options const&) = default;
92  Options(Options&&) = default;
93  Options& operator=(Options&&) = default;
94 
95  /**
96  * Sets option `T` to the value @p v and returns a reference to `*this`.
97  *
98  * @code
99  * struct FooOption {
100  * using Type = int;
101  * };
102  * auto opts = Options{}.set<FooOption>(123);
103  * @endcode
104  *
105  * @tparam T the option type
106  * @param v the value to set the option T
107  */
108  template <typename T>
109  Options& set(ValueTypeT<T> v) {
110  m_[typeid(T)] = Data<T>{std::move(v)};
111  return *this;
112  }
113 
114  /**
115  * Returns true IFF an option with type `T` exists.
116  *
117  * @tparam T the option type
118  */
119  template <typename T>
120  bool has() const {
121  return m_.find(typeid(T)) != m_.end();
122  }
123 
124  /**
125  * Erases the option specified by the type `T`.
126  *
127  * @tparam T the option type
128  */
129  template <typename T>
130  void unset() {
131  m_.erase(typeid(T));
132  }
133 
134  /**
135  * Returns a reference to the value for `T`, or a value-initialized default
136  * if `T` was not set.
137  *
138  * This method will always return a reference to a valid value of the correct
139  * type for option `T`, whether or not `T` has actually been set. Use
140  * `has<T>()` to check whether or not the option has been set.
141  *
142  * @code
143  * struct FooOption {
144  * using Type = std::set<std::string>;
145  * };
146  * Options opts;
147  * std::set<std::string> const& x = opts.get<FooOption>();
148  * assert(x.empty());
149  * assert(!x.has<FooOption>());
150  *
151  * opts.set<FooOption>({"foo"});
152  * assert(opts.get<FooOption>().size() == 1);
153  * @endcode
154  *
155  * @tparam T the option type
156  */
157  template <typename T>
158  ValueTypeT<T> const& get() const {
159  static auto const* const kDefaultValue = new ValueTypeT<T>{};
160  auto const it = m_.find(typeid(T));
161  if (it != m_.end()) return absl::any_cast<Data<T>>(&it->second)->value;
162  return *kDefaultValue;
163  }
164 
165  /**
166  * Returns a reference to the value for option `T`, setting the value to @p
167  * init_value if necessary.
168  *
169  * @code
170  * struct BigOption {
171  * using Type = std::set<std::string>;
172  * };
173  * Options opts;
174  * std::set<std::string>& x = opts.lookup<BigOption>();
175  * assert(x.empty());
176  *
177  * x.insert("foo");
178  * opts.lookup<BigOption>().insert("bar");
179  * assert(x.size() == 2);
180  * @endcode
181  *
182  * @tparam T the option type
183  * @param init_value the initial value to use if `T` is not set (optional)
184  */
185  template <typename T>
186  ValueTypeT<T>& lookup(ValueTypeT<T> init_value = {}) {
187  auto const p = m_.emplace(typeid(T), Data<T>{std::move(init_value)});
188  return absl::any_cast<Data<T>>(&p.first->second)->value;
189  }
190 
191  private:
192  friend Options internal::MergeOptions(Options, Options);
193  friend void internal::CheckExpectedOptionsImpl(
194  std::set<std::type_index> const&, Options const&, char const*);
195 
196  // The data holder for all the option values.
197  template <typename T>
198  struct Data {
199  ValueTypeT<T> value;
200  };
201 
202  // The `absl::any` objects all hold a `Data<T>`
203  std::unordered_map<std::type_index, absl::any> m_;
204 };
205 
206 /**
207  * A template to hold a list of "option" types.
208  *
209  * This can be a useful way to create meaningful lists of options. For example,
210  * there could be a list containing all the gRPC options. Or a list of all
211  * ProductX options. This gives us a way to link to lists of options with
212  * doxygen, and to do some checking about what options a function may expect.
213  */
214 template <typename... T>
215 using OptionList = internal::TypeList<T...>;
216 
217 namespace internal {
218 
219 // Wraps `T` in a `OptionList`, unless it was already one.
220 template <typename T>
221 struct WrapTypeList {
222  using Type = OptionList<T>;
223 };
224 template <typename... T>
225 struct WrapTypeList<OptionList<T...>> {
226  using Type = OptionList<T...>; // Note: Doesn't work w/ nested OptionLists.
227 };
228 template <typename T>
229 using WrapTypeListT = typename WrapTypeList<T>::Type;
230 
231 template <typename... T>
232 void CheckExpectedOptionsImpl(OptionList<T...> const&, Options const& opts,
233  char const* caller) {
234  CheckExpectedOptionsImpl({typeid(T)...}, opts, caller);
235 }
236 
237 /**
238  * Checks that `Options` only contains the given expected options or a subset
239  * of them.
240  *
241  * Logs all unexpected options. Note that logging is not always shown
242  * on the console. Set the environment variable
243  * `GOOGLE_CLOUD_CPP_ENABLE_CLOG=yes` to enable logging.
244  *
245  * Options may be specified directly or as a collection in an `OptionList`.
246  * For example,
247  *
248  * @code
249  * struct FooOption { int value; };
250  * struct BarOption { int value; };
251  * using MyOptions = OptionList<FooOption, BarOption>;
252  *
253  * struct BazOption { int value; };
254  *
255  * // All valid ways to call this with varying expectations.
256  * CheckExpectedOptions<FooOption>(opts, "test caller");
257  * CheckExpectedOptions<FooOption, BarOption>(opts, "test caller");
258  * CheckExpectedOptions<MyOptions>(opts, "test caller");
259  * CheckExpectedOptions<BazOption, MyOptions>(opts, "test caller");
260  * @endcode
261  *
262  * @param opts the `Options` to check.
263  * @param caller some string indicating the callee function; logged IFF there's
264  * an unexpected option
265  */
266 template <typename... T>
267 void CheckExpectedOptions(Options const& opts, char const* caller) {
268  using ExpectedTypes = TypeListCatT<WrapTypeListT<T>...>;
269  CheckExpectedOptionsImpl(ExpectedTypes{}, opts, caller);
270 }
271 
272 /**
273  * Moves the options from @p b into @p a and returns the result, unless the
274  * option already exists in @p a.
275  */
276 Options MergeOptions(Options a, Options b);
277 
278 } // namespace internal
279 
280 } // namespace GOOGLE_CLOUD_CPP_NS
281 } // namespace cloud
282 } // namespace google
283 
284 #endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_OPTIONS_H