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
10#include <viam/sdk/common/proto_convert.hpp>
11
12namespace google {
13namespace protobuf {
14
15// Forward declaration of google::protobuf Value and Struct.
16// The class below is written so as to keep Value out of the ABI, and as such can be instantiated
17// with Value as an incomplete type.
18
19class Value;
20class Struct;
21} // namespace protobuf
22} // namespace google
23
24namespace viam {
25namespace sdk {
26
27namespace proto_value_details {
28
30 move_may_throw(move_may_throw&&) noexcept(false) = default;
31 move_may_throw& operator=(move_may_throw&&) noexcept(false) = default;
32};
33
34// Type trait for determining if move operations on ProtoValue are noexcept.
35// Conditional noexcept-ness of move construction on vector and unordered_map is not
36// guaranteed until c++17, and even then not fully. Thus define a traits class to check whether the
37// containers have nothrow move ops, and use that to determine if ProtoValue's move ops are nothrow.
38// We use move_may_throw as an illustrative dummy class since, perhaps surprisingly, nothrow
39// movability of vector and unordered_map doesn't depend on their stored type.
41 : std::integral_constant<bool,
42 std::is_nothrow_move_constructible<std::vector<move_may_throw>>{} &&
43 std::is_nothrow_move_constructible<
44 std::unordered_map<std::string, move_may_throw>>{}> {};
45
46} // namespace proto_value_details
47
56 public:
58
60 enum Kind { k_null = 0, k_bool = 1, k_double = 2, k_string = 3, k_list = 4, k_struct = 5 };
61
63 ProtoValue() noexcept;
64
68
69 ProtoValue(std::nullptr_t) noexcept;
70
71 ProtoValue(bool b) noexcept;
72
74 ProtoValue(int i) noexcept;
75
76 ProtoValue(double d) noexcept;
77
78 ProtoValue(std::string s) noexcept;
79
81 ProtoValue(const char* str);
82
84 template <typename Val = ProtoValue,
85 typename = std::enable_if_t<std::is_same<Val, ProtoValue>{}>>
86 ProtoValue(std::vector<Val>) noexcept(std::is_nothrow_move_constructible<std::vector<Val>>{});
87
89 template <typename Val = ProtoValue,
90 typename = std::enable_if_t<std::is_same<Val, ProtoValue>{}>>
91 ProtoValue(std::unordered_map<std::string, Val>) noexcept(
92 std::is_nothrow_move_constructible<std::unordered_map<std::string, Val>>{});
93
95
99
100 ProtoValue(const ProtoValue& other);
101
105
106 ProtoValue& operator=(const ProtoValue& other);
107
108 ~ProtoValue();
109
113 friend bool operator==(const ProtoValue& lhs, const ProtoValue& rhs);
114
115 void swap(ProtoValue& other) noexcept(proto_value_details::all_moves_noexcept{});
116
119
121 Kind kind() const;
122
124 template <typename T>
125 bool is_a() const;
126
128 bool is_null() const;
129
131 template <typename T>
132 T* get();
133
135 template <typename T>
136 T const* get() const;
137
140 template <typename T>
141 std::enable_if_t<std::is_scalar<T>{}, T&> get_unchecked();
142
145 template <typename T>
146 std::enable_if_t<std::is_scalar<T>{}, T> get_unchecked() const;
147
150 template <typename T>
151 std::enable_if_t<!std::is_scalar<T>{}, T&> get_unchecked() &;
152
155 template <typename T>
156 std::enable_if_t<!std::is_scalar<T>{}, T const&> get_unchecked() const&;
157
160 template <typename T>
161 std::enable_if_t<!std::is_scalar<T>{}, T&&> get_unchecked() &&;
162
164
165 private:
166 // This struct is our implementation of a virtual table, similar to what is created by the
167 // compiler for polymorphic types. We can't use actual polymorphic types because the
168 // implementation uses aligned stack storage, so we DIY it insted.
169 // The vtable can be thought of as defining a concept or interface which our ProtoValue
170 // types must satisfy. The first void [const]* parameter in any of the pointers below is
171 // always the `this` or `self` pointer.
172 struct vtable {
173 void (*dtor)(void*);
174 void (*copy)(void const*, void*);
175 void (*move)(void*, void*);
176 void (*to_value)(void const*, google::protobuf::Value*);
177 Kind (*kind)();
178 bool (*equal_to)(void const*, void const*, const vtable&);
179 };
180
181 // Class template for a concrete model of the concept described by vtable.
182 // All construction and equality operations are implemented using defaults of the stored T.
183 template <typename T>
184 struct model {
185 static_assert(std::is_nothrow_destructible<T>{}, "T has a throwing destructor");
186
187 model(T t) noexcept(std::is_nothrow_move_constructible<T>{});
188
189 static void dtor(void* self) noexcept;
190
191 static void copy(void const* self, void* dest);
192
193 // Clang bug prevents us from marking this noexcept but it's being called through a
194 // non-noexcept pointer anyway
195 static void move(void* self, void* dest);
196
197 static void to_value(void const* self, google::protobuf::Value* v);
198
199 static Kind kind() noexcept;
200
201 static bool equal_to(void const* self, void const* other, const vtable& other_vtable);
202
203 static constexpr vtable vtable_{dtor, copy, move, to_value, kind, equal_to};
204 T data;
205 };
206
207 // Stack-based storage for instances of model<T>.
208 // This is an RAII class which manages lifetime, pointer access, and construction of a
209 // type-erased model<T>. Note that all lifetime operations require a vtable to which the
210 // implementation defers.
211 struct storage {
212 // Local storage in an aligned_union which can hold all the possible ProtoValue types, using
213 // stand-ins for ProtoList and ProtoStruct which are not available until end
214 // of class definition.
215 using BufType = std::aligned_union_t<0,
216 std::nullptr_t,
217 bool,
218 double,
219 std::string,
220 std::vector<void*>,
221 std::unordered_map<std::string, void*>>;
222
223 static constexpr std::size_t local_storage_size = sizeof(BufType);
224 static constexpr std::size_t local_storage_alignment = alignof(BufType);
225
226 // Construct this to store a model<T>.
227 template <typename T>
228 storage(T t) noexcept(std::is_nothrow_move_constructible<T>{});
229
230 // These default special member functions are deleted in favor of overloads below which take
231 // a vtable parameter.
232 // We need the vtable because otherwise we have no way to perform the requisite copy, move,
233 // or destructor operations.
234 storage(const storage&) = delete;
235 storage(storage&&) = delete;
236 storage& operator=(const storage&) = delete;
237 storage& operator=(storage&&) = delete;
238
239 // Copy construct this storage from other, using the copy operation in vtable.
240 storage(const storage& other, const vtable& vtable);
241
242 // Move construct this storage from other, using the move operation in vtable.
243 storage(storage&& other,
244 const vtable& vtable) noexcept(proto_value_details::all_moves_noexcept{});
245
246 // Swap this storage with other, with operations on this provided by this_vtable, and
247 // operations on other provided by other_vtable.
248 void swap(const vtable& this_vtable,
249 storage& other,
250 const vtable& other_vtable) noexcept(proto_value_details::all_moves_noexcept{});
251
252 // Destroy this storage, using the destructor from vtable.
253 void destruct(const vtable& vtable) noexcept;
254
255 template <typename T = void>
256 T* get() {
257 return static_cast<T*>(static_cast<void*>(&buf_));
258 }
259
260 template <typename T = void>
261 T const* get() const {
262 return static_cast<T const*>(static_cast<void const*>(&buf_));
263 }
264
265 BufType buf_;
266 };
267
268 ProtoValue(const google::protobuf::Value* value);
269
270 // Helper template for the explicit versions above.
271 // Includes nullptr_t as a tag type so we can let the other constructors delegate.
272 template <typename T>
273 ProtoValue(T t, std::nullptr_t) noexcept(std::is_nothrow_move_constructible<T>{});
274
275 vtable vtable_;
276 storage self_;
277};
278
279// Pre c++17 this is still required
280template <typename T>
281constexpr ProtoValue::vtable ProtoValue::model<T>::vtable_;
282
285using ProtoList = std::vector<ProtoValue>;
286
290using ProtoStruct = std::unordered_map<std::string, ProtoValue>;
291
292// -- Template specialization declarations of by-value constructors -- //
293extern template ProtoValue::ProtoValue(ProtoList) noexcept(
294 std::is_nothrow_move_constructible<ProtoList>{});
295extern template ProtoValue::ProtoValue(ProtoStruct m) noexcept(
296 std::is_nothrow_move_constructible<ProtoStruct>{});
297
298// -- Template specialization declarations of get_unchecked: POD types -- //
299extern template bool& ProtoValue::get_unchecked<bool>();
300extern template double& ProtoValue::get_unchecked<double>();
301
302extern template bool ProtoValue::get_unchecked<bool>() const;
303extern template double ProtoValue::get_unchecked<double>() const;
304
305// -- Template specialization declarations of get_unchecked: string and recursive types -- //
306extern template std::string& ProtoValue::get_unchecked<std::string>() &;
307extern template ProtoList& ProtoValue::get_unchecked<ProtoList>() &;
308extern template ProtoStruct& ProtoValue::get_unchecked<ProtoStruct>() &;
309
310extern template std::string const& ProtoValue::get_unchecked<std::string>() const&;
311extern template ProtoList const& ProtoValue::get_unchecked<ProtoList>() const&;
312extern template ProtoStruct const& ProtoValue::get_unchecked<ProtoStruct>() const&;
313
314extern template std::string&& ProtoValue::get_unchecked<std::string>() &&;
315extern template ProtoList&& ProtoValue::get_unchecked<ProtoList>() &&;
316extern template ProtoStruct&& ProtoValue::get_unchecked<ProtoStruct>() &&;
317
318namespace proto_convert_details {
319
320template <>
321struct to_proto_impl<ProtoValue> {
322 void operator()(const ProtoValue&, google::protobuf::Value*) const;
323};
324
325template <>
326struct to_proto_impl<ProtoStruct> {
327 void operator()(const ProtoStruct&, google::protobuf::Struct*) const;
328};
329
330template <>
331struct from_proto_impl<google::protobuf::Value> {
332 ProtoValue operator()(const google::protobuf::Value*) const;
333};
334
335template <>
336struct from_proto_impl<google::protobuf::Struct> {
337 ProtoStruct operator()(const google::protobuf::Struct*) const;
338};
339
340} // namespace proto_convert_details
341
342namespace proto_value_details {
343
344void to_value(std::nullptr_t, google::protobuf::Value* v);
345void to_value(bool b, google::protobuf::Value* v);
346void to_value(double d, google::protobuf::Value* v);
347void to_value(std::string s, google::protobuf::Value* v);
348void to_value(const ProtoList& vec, google::protobuf::Value* v);
349void to_value(const ProtoStruct& m, google::protobuf::Value* v);
350
351template <typename T>
352struct kind {};
353
354template <ProtoValue::Kind k>
355using KindConstant = std::integral_constant<ProtoValue::Kind, k>;
356
357template <>
358struct kind<std::nullptr_t> {
359 using type = KindConstant<ProtoValue::Kind::k_null>;
360};
361
362template <>
363struct kind<bool> {
364 using type = KindConstant<ProtoValue::Kind::k_bool>;
365};
366
367template <>
368struct kind<double> {
369 using type = KindConstant<ProtoValue::Kind::k_double>;
370};
371
372template <>
373struct kind<std::string> {
374 using type = KindConstant<ProtoValue::Kind::k_string>;
375};
376
377template <>
378struct kind<ProtoList> {
379 using type = KindConstant<ProtoValue::Kind::k_list>;
380};
381
382template <>
383struct kind<ProtoStruct> {
384 using type = KindConstant<ProtoValue::Kind::k_struct>;
385};
386
387} // namespace proto_value_details
388
389template <typename T>
393
394template <typename T>
396 if (this->is_a<T>()) {
397 return this->self_.template get<T>();
398 }
399
400 return nullptr;
401}
402
403template <typename T>
404T const* ProtoValue::get() const {
405 if (this->is_a<T>()) {
406 return this->self_.template get<T>();
407 }
408
409 return nullptr;
410}
411
412} // namespace sdk
413} // namespace viam
Type-erased value for storing google::protobuf::Value types. A ProtoValue can be nullptr,...
Definition proto_value.hpp:55
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:395
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:390
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:60
Definition proto_value.hpp:352