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