Viam C++ SDK current
Loading...
Searching...
No Matches
proto_value.hpp
1#pragma once
2
3#include <memory>
4#include <string>
5#include <type_traits>
6#include <unordered_map>
7#include <utility>
8#include <vector>
9
10namespace google {
11namespace protobuf {
12
13// Forward declaration of google::protobuf Value and Struct.
14// The class below is written so as to keep Value out of the ABI, and as such can be instantiated
15// with Value as an incomplete type.
16
17class Value;
18class Struct;
19} // namespace protobuf
20} // namespace google
21
22namespace viam {
23namespace sdk {
24
25namespace proto_value_details {
26
28 move_may_throw(move_may_throw&&) noexcept(false) = default;
29 move_may_throw& operator=(move_may_throw&&) noexcept(false) = default;
30};
31
32// Type trait for determining if move operations on ProtoValue are noexcept.
33// Conditional noexcept-ness of move construction on vector and unordered_map is not
34// guaranteed until c++17, and even then not fully. Thus define a traits class to check whether the
35// containers have nothrow move ops, and use that to determine if ProtoValue's move ops are nothrow.
36// We use move_may_throw as an illustrative dummy class since, perhaps surprisingly, nothrow
37// movability of vector and unordered_map doesn't depend on their stored type.
39 : std::integral_constant<bool,
40 std::is_nothrow_move_constructible<std::vector<move_may_throw>>{} &&
41 std::is_nothrow_move_constructible<
42 std::unordered_map<std::string, move_may_throw>>{}> {};
43
44} // namespace proto_value_details
45
54 public:
56 enum Kind { k_null = 0, k_bool = 1, k_double = 2, k_string = 3, k_list = 4, k_struct = 5 };
57
59 ProtoValue() noexcept;
60
64
65 ProtoValue(std::nullptr_t) noexcept;
66
67 ProtoValue(bool b) noexcept;
68
70 ProtoValue(int i) noexcept;
71
72 ProtoValue(double d) noexcept;
73
74 ProtoValue(std::string s) noexcept;
75
77 ProtoValue(const char* str);
78
80 template <typename Val = ProtoValue,
81 typename = std::enable_if_t<std::is_same<Val, ProtoValue>{}>>
82 ProtoValue(std::vector<Val>) noexcept(std::is_nothrow_move_constructible<std::vector<Val>>{});
83
85 template <typename Val = ProtoValue,
86 typename = std::enable_if_t<std::is_same<Val, ProtoValue>{}>>
87 ProtoValue(std::unordered_map<std::string, Val>) noexcept(
88 std::is_nothrow_move_constructible<std::unordered_map<std::string, Val>>{});
89
91
95
96 ProtoValue(const ProtoValue& other);
97
101
102 ProtoValue& operator=(const ProtoValue& other);
103
104 ~ProtoValue();
105
109 friend bool operator==(const ProtoValue& lhs, const ProtoValue& rhs);
110
111 void swap(ProtoValue& other) noexcept(proto_value_details::all_moves_noexcept{});
112
117 template <typename Value = google::protobuf::Value>
118 static ProtoValue from_proto(const Value& v); // NOLINT(misc-no-recursion)
119
120 friend void to_proto(const ProtoValue& t, google::protobuf::Value* v);
121
124
126 Kind kind() const;
127
129 template <typename T>
130 bool is_a() const;
131
133 bool is_null() const;
134
136 template <typename T>
137 T* get();
138
140 template <typename T>
141 T const* get() const;
142
145 template <typename T>
146 std::enable_if_t<std::is_scalar<T>{}, T&> get_unchecked();
147
150 template <typename T>
151 std::enable_if_t<std::is_scalar<T>{}, T> get_unchecked() const;
152
155 template <typename T>
156 std::enable_if_t<!std::is_scalar<T>{}, T&> get_unchecked() &;
157
160 template <typename T>
161 std::enable_if_t<!std::is_scalar<T>{}, T const&> get_unchecked() const&;
162
165 template <typename T>
166 std::enable_if_t<!std::is_scalar<T>{}, T&&> get_unchecked() &&;
167
169
170 private:
171 // This struct is our implementation of a virtual table, similar to what is created by the
172 // compiler for polymorphic types. We can't use actual polymorphic types because the
173 // implementation uses aligned stack storage, so we DIY it insted.
174 // The vtable can be thought of as defining a concept or interface which our ProtoValue
175 // types must satisfy. The first void [const]* parameter in any of the pointers below is
176 // always the `this` or `self` pointer.
177 struct vtable {
178 void (*dtor)(void*);
179 void (*copy)(void const*, void*);
180 void (*move)(void*, void*);
181 void (*to_proto)(void const*, google::protobuf::Value*);
182 Kind (*kind)();
183 bool (*equal_to)(void const*, void const*, const vtable&);
184 };
185
186 // Class template for a concrete model of the concept described by vtable.
187 // All construction and equality operations are implemented using defaults of the stored T.
188 template <typename T>
189 struct model {
190 static_assert(std::is_nothrow_destructible<T>{}, "T has a throwing destructor");
191
192 model(T t) noexcept(std::is_nothrow_move_constructible<T>{});
193
194 static void dtor(void* self) noexcept;
195
196 static void copy(void const* self, void* dest);
197
198 // Clang bug prevents us from marking this noexcept but it's being called through a
199 // non-noexcept pointer anyway
200 static void move(void* self, void* dest);
201
202 static void to_proto(void const* self, google::protobuf::Value* v);
203
204 static Kind kind() noexcept;
205
206 static bool equal_to(void const* self, void const* other, const vtable& other_vtable);
207
208 static constexpr vtable vtable_{dtor, copy, move, to_proto, kind, equal_to};
209 T data;
210 };
211
212 // Stack-based storage for instances of model<T>.
213 // This is an RAII class which manages lifetime, pointer access, and construction of a
214 // type-erased model<T>. Note that all lifetime operations require a vtable to which the
215 // implementation defers.
216 struct storage {
217 // Local storage in an aligned_union which can hold all the possible ProtoValue types, using
218 // stand-ins for ProtoList and ProtoStruct which are not available until end
219 // of class definition.
220 using BufType = std::aligned_union_t<0,
221 std::nullptr_t,
222 bool,
223 double,
224 std::string,
225 std::vector<void*>,
226 std::unordered_map<std::string, void*>>;
227
228 static constexpr std::size_t local_storage_size = sizeof(BufType);
229 static constexpr std::size_t local_storage_alignment = alignof(BufType);
230
231 // Construct this to store a model<T>.
232 template <typename T>
233 storage(T t) noexcept(std::is_nothrow_move_constructible<T>{});
234
235 // These default special member functions are deleted in favor of overloads below which take
236 // a vtable parameter.
237 // We need the vtable because otherwise we have no way to perform the requisite copy, move,
238 // or destructor operations.
239 storage(const storage&) = delete;
240 storage(storage&&) = delete;
241 storage& operator=(const storage&) = delete;
242 storage& operator=(storage&&) = delete;
243
244 // Copy construct this storage from other, using the copy operation in vtable.
245 storage(const storage& other, const vtable& vtable);
246
247 // Move construct this storage from other, using the move operation in vtable.
248 storage(storage&& other,
249 const vtable& vtable) noexcept(proto_value_details::all_moves_noexcept{});
250
251 // Swap this storage with other, with operations on this provided by this_vtable, and
252 // operations on other provided by other_vtable.
253 void swap(const vtable& this_vtable,
254 storage& other,
255 const vtable& other_vtable) noexcept(proto_value_details::all_moves_noexcept{});
256
257 // Destroy this storage, using the destructor from vtable.
258 void destruct(const vtable& vtable) noexcept;
259
260 template <typename T = void>
261 T* get() {
262 return static_cast<T*>(static_cast<void*>(&buf_));
263 }
264
265 template <typename T = void>
266 T const* get() const {
267 return static_cast<T const*>(static_cast<void const*>(&buf_));
268 }
269
270 BufType buf_;
271 };
272
273 ProtoValue(const google::protobuf::Value* value);
274
275 // Helper template for the explicit versions above.
276 // Includes nullptr_t as a tag type so we can let the other constructors delegate.
277 template <typename T>
278 ProtoValue(T t, std::nullptr_t) noexcept(std::is_nothrow_move_constructible<T>{});
279
280 vtable vtable_;
281 storage self_;
282};
283
284// Pre c++17 this is still required
285template <typename T>
286constexpr ProtoValue::vtable ProtoValue::model<T>::vtable_;
287
290using ProtoList = std::vector<ProtoValue>;
291
295using ProtoStruct = std::unordered_map<std::string, ProtoValue>;
296
297// -- Template specialization declarations of by-value constructors -- //
298extern template ProtoValue::ProtoValue(ProtoList) noexcept(
299 std::is_nothrow_move_constructible<ProtoList>{});
300extern template ProtoValue::ProtoValue(ProtoStruct m) noexcept(
301 std::is_nothrow_move_constructible<ProtoStruct>{});
302
303// -- Template specialization declarations of get_unchecked: POD types -- //
304extern template bool& ProtoValue::get_unchecked<bool>();
305extern template double& ProtoValue::get_unchecked<double>();
306
307extern template bool ProtoValue::get_unchecked<bool>() const;
308extern template double ProtoValue::get_unchecked<double>() const;
309
310// -- Template specialization declarations of get_unchecked: string and recursive types -- //
311extern template std::string& ProtoValue::get_unchecked<std::string>() &;
312extern template ProtoList& ProtoValue::get_unchecked<ProtoList>() &;
313extern template ProtoStruct& ProtoValue::get_unchecked<ProtoStruct>() &;
314
315extern template std::string const& ProtoValue::get_unchecked<std::string>() const&;
316extern template ProtoList const& ProtoValue::get_unchecked<ProtoList>() const&;
317extern template ProtoStruct const& ProtoValue::get_unchecked<ProtoStruct>() const&;
318
319extern template std::string&& ProtoValue::get_unchecked<std::string>() &&;
320extern template ProtoList&& ProtoValue::get_unchecked<ProtoList>() &&;
321extern template ProtoStruct&& ProtoValue::get_unchecked<ProtoStruct>() &&;
322
323void to_proto(std::nullptr_t, google::protobuf::Value* v);
324void to_proto(bool b, google::protobuf::Value* v);
325void to_proto(double d, google::protobuf::Value* v);
326void to_proto(std::string s, google::protobuf::Value* v);
327void to_proto(const ProtoList& vec, google::protobuf::Value* v);
328void to_proto(const ProtoStruct& m, google::protobuf::Value* v);
329void to_proto(const ProtoValue& t, google::protobuf::Value* v);
330
331void struct_to_map(google::protobuf::Struct const* s, ProtoStruct& m);
332void map_to_struct(const ProtoStruct& m, google::protobuf::Struct* s);
333
338template <typename Value = google::protobuf::Value>
339Value to_proto(const ProtoValue& proto_value) {
340 Value v;
341 to_proto(proto_value, &v);
342
343 return v;
344}
345
350template <typename Struct = google::protobuf::Struct>
351ProtoStruct struct_to_map(const Struct& s) {
352 ProtoStruct result;
353 struct_to_map(&s, result);
354
355 return result;
356}
357
362template <typename Struct = google::protobuf::Struct>
363Struct map_to_struct(const ProtoStruct& m) {
364 Struct s;
365 map_to_struct(m, &s);
366
367 return s;
368}
369
370namespace proto_value_details {
371
372template <typename T>
373struct kind {};
374
375template <ProtoValue::Kind k>
376using KindConstant = std::integral_constant<ProtoValue::Kind, k>;
377
378template <>
379struct kind<std::nullptr_t> {
380 using type = KindConstant<ProtoValue::Kind::k_null>;
381};
382
383template <>
384struct kind<bool> {
385 using type = KindConstant<ProtoValue::Kind::k_bool>;
386};
387
388template <>
389struct kind<double> {
390 using type = KindConstant<ProtoValue::Kind::k_double>;
391};
392
393template <>
394struct kind<std::string> {
395 using type = KindConstant<ProtoValue::Kind::k_string>;
396};
397
398template <>
399struct kind<ProtoList> {
400 using type = KindConstant<ProtoValue::Kind::k_list>;
401};
402
403template <>
404struct kind<ProtoStruct> {
405 using type = KindConstant<ProtoValue::Kind::k_struct>;
406};
407
408} // namespace proto_value_details
409
410template <typename T>
414
415template <typename T>
417 if (this->is_a<T>()) {
418 return this->self_.template get<T>();
419 }
420
421 return nullptr;
422}
423
424template <typename T>
425T const* ProtoValue::get() const {
426 if (this->is_a<T>()) {
427 return this->self_.template get<T>();
428 }
429
430 return nullptr;
431}
432
433} // namespace sdk
434} // namespace viam
Type-erased value for storing google::protobuf::Value types. A ProtoValue can be nullptr,...
Definition proto_value.hpp:53
static ProtoValue from_proto(const Value &v)
Construct from proto value.
bool is_null() const
Convenience version of is_a<T> to check if the value is nullptr.
ProtoValue(ProtoValue &&other) noexcept(proto_value_details::all_moves_noexcept{})
Move construct this from other, leaving other in its unspecified-but-valid moved from state.
T * get()
Return a T pointer if this is_a<T>(), else return nullptr.
Definition proto_value.hpp:416
std::enable_if_t< std::is_scalar< T >{}, T & > get_unchecked()
Return a reference to the underlying T, without checking.
ProtoValue & operator=(ProtoValue &&other) noexcept(proto_value_details::all_moves_noexcept{})
Move assignment from other, leaving other in its unspecified-but-valid moved from state.
ProtoValue() noexcept
Construct a null object.
bool is_a() const
Checks whether this ProtoT is an instance of type T.
Definition proto_value.hpp:411
friend bool operator==(const ProtoValue &lhs, const ProtoValue &rhs)
Test equality of two types.
Kind kind() const
Obtain enumerator constant representing the stored data type.
ProtoValue(std::unordered_map< std::string, Val >) noexcept(std::is_nothrow_move_constructible< std::unordered_map< std::string, Val > >{})
Construct from a ProtoStruct.
ProtoValue(std::vector< Val >) noexcept(std::is_nothrow_move_constructible< std::vector< Val > >{})
Construct from a ProtoList.
T const * get() const
Return a T pointer if this is_a<T>(), else return nullptr.
Kind
Type discriminator constants for possible values stored in a ProtoValue.
Definition proto_value.hpp:56
Definition proto_value.hpp:373