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
139 template <typename T>
141
143 template <typename T>
144 const T& get_unchecked() const&;
145
147 template <typename T>
149
151
152 private:
153 // This struct is our implementation of a virtual table, similar to what is created by the
154 // compiler for polymorphic types. We can't use actual polymorphic types because the
155 // implementation uses aligned stack storage, so we DIY it insted.
156 // The vtable can be thought of as defining a concept or interface which our ProtoValue
157 // types must satisfy. The first void [const]* parameter in any of the pointers below is
158 // always the `this` or `self` pointer.
159 struct vtable {
160 void (*dtor)(void*);
161 void (*copy)(void const*, void*);
162 void (*move)(void*, void*);
163 void (*to_value)(void const*, google::protobuf::Value*);
164 Kind (*kind)();
165 bool (*equal_to)(void const*, void const*, const vtable&);
166 };
167
168 // Class template for a concrete model of the concept described by vtable.
169 // All construction and equality operations are implemented using defaults of the stored T.
170 template <typename T>
171 struct model {
172 static_assert(std::is_nothrow_destructible<T>{}, "T has a throwing destructor");
173
174 model(T t) noexcept(std::is_nothrow_move_constructible<T>{});
175
176 static void dtor(void* self) noexcept;
177
178 static void copy(void const* self, void* dest);
179
180 // Clang bug prevents us from marking this noexcept but it's being called through a
181 // non-noexcept pointer anyway
182 static void move(void* self, void* dest);
183
184 static void to_value(void const* self, google::protobuf::Value* v);
185
186 static Kind kind() noexcept;
187
188 static bool equal_to(void const* self, void const* other, const vtable& other_vtable);
189
190 static constexpr vtable vtable_{dtor, copy, move, to_value, kind, equal_to};
191 T data;
192 };
193
194 // Stack-based storage for instances of model<T>.
195 // This is an RAII class which manages lifetime, pointer access, and construction of a
196 // type-erased model<T>. Note that all lifetime operations require a vtable to which the
197 // implementation defers.
198 struct storage {
199 // Local storage in an aligned_union which can hold all the possible ProtoValue types, using
200 // stand-ins for ProtoList and ProtoStruct which are not available until end
201 // of class definition.
202 using BufType = std::aligned_union_t<0,
203 std::nullptr_t,
204 bool,
205 double,
206 std::string,
207 std::vector<void*>,
208 std::unordered_map<std::string, void*>>;
209
210 static constexpr std::size_t local_storage_size = sizeof(BufType);
211 static constexpr std::size_t local_storage_alignment = alignof(BufType);
212
213 // Construct this to store a model<T>.
214 template <typename T>
215 storage(T t) noexcept(std::is_nothrow_move_constructible<T>{});
216
217 // These default special member functions are deleted in favor of overloads below which take
218 // a vtable parameter.
219 // We need the vtable because otherwise we have no way to perform the requisite copy, move,
220 // or destructor operations.
221 storage(const storage&) = delete;
222 storage(storage&&) = delete;
223 storage& operator=(const storage&) = delete;
224 storage& operator=(storage&&) = delete;
225
226 // Copy construct this storage from other, using the copy operation in vtable.
227 storage(const storage& other, const vtable& vtable);
228
229 // Move construct this storage from other, using the move operation in vtable.
230 storage(storage&& other,
231 const vtable& vtable) noexcept(proto_value_details::all_moves_noexcept{});
232
233 // Swap this storage with other, with operations on this provided by this_vtable, and
234 // operations on other provided by other_vtable.
235 void swap(const vtable& this_vtable,
236 storage& other,
237 const vtable& other_vtable) noexcept(proto_value_details::all_moves_noexcept{});
238
239 // Destroy this storage, using the destructor from vtable.
240 void destruct(const vtable& vtable) noexcept;
241
242 template <typename T = void>
243 T* get() {
244 return static_cast<T*>(static_cast<void*>(&buf_));
245 }
246
247 template <typename T = void>
248 T const* get() const {
249 return static_cast<T const*>(static_cast<void const*>(&buf_));
250 }
251
252 BufType buf_;
253 };
254
255 ProtoValue(const google::protobuf::Value* value);
256
257 // Helper template for the explicit versions above.
258 // Includes nullptr_t as a tag type so we can let the other constructors delegate.
259 template <typename T>
260 ProtoValue(T t, std::nullptr_t) noexcept(std::is_nothrow_move_constructible<T>{});
261
262 vtable vtable_;
263 storage self_;
264};
265
266// Pre c++17 this is still required
267template <typename T>
268constexpr ProtoValue::vtable ProtoValue::model<T>::vtable_;
269
272using ProtoList = std::vector<ProtoValue>;
273
277using ProtoStruct = std::unordered_map<std::string, ProtoValue>;
278
279// -- Template specialization declarations of by-value constructors -- //
280extern template ProtoValue::ProtoValue(ProtoList) noexcept(
281 std::is_nothrow_move_constructible<ProtoList>{});
282extern template ProtoValue::ProtoValue(ProtoStruct m) noexcept(
283 std::is_nothrow_move_constructible<ProtoStruct>{});
284
285// -- Template specialization declarations of get_unchecked -- //
286extern template bool& ProtoValue::get_unchecked<bool>() &;
287extern template double& ProtoValue::get_unchecked<double>() &;
288extern template std::string& ProtoValue::get_unchecked<std::string>() &;
289extern template ProtoList& ProtoValue::get_unchecked<ProtoList>() &;
290extern template ProtoStruct& ProtoValue::get_unchecked<ProtoStruct>() &;
291
292extern template bool const& ProtoValue::get_unchecked<bool>() const&;
293extern template double const& ProtoValue::get_unchecked<double>() const&;
294extern template std::string const& ProtoValue::get_unchecked<std::string>() const&;
295extern template ProtoList const& ProtoValue::get_unchecked<ProtoList>() const&;
296extern template ProtoStruct const& ProtoValue::get_unchecked<ProtoStruct>() const&;
297
298extern template bool&& ProtoValue::get_unchecked<bool>() &&;
299extern template double&& ProtoValue::get_unchecked<double>() &&;
300extern template std::string&& ProtoValue::get_unchecked<std::string>() &&;
301extern template ProtoList&& ProtoValue::get_unchecked<ProtoList>() &&;
302extern template ProtoStruct&& ProtoValue::get_unchecked<ProtoStruct>() &&;
303
304namespace proto_convert_details {
305
306template <>
307struct to_proto_impl<ProtoValue> {
308 void operator()(const ProtoValue&, google::protobuf::Value*) const;
309};
310
311template <>
312struct to_proto_impl<ProtoStruct> {
313 void operator()(const ProtoStruct&, google::protobuf::Struct*) const;
314};
315
316template <>
317struct from_proto_impl<google::protobuf::Value> {
318 ProtoValue operator()(const google::protobuf::Value*) const;
319};
320
321template <>
322struct from_proto_impl<google::protobuf::Struct> {
323 ProtoStruct operator()(const google::protobuf::Struct*) const;
324};
325
326} // namespace proto_convert_details
327
328namespace proto_value_details {
329
330void to_value(std::nullptr_t, google::protobuf::Value* v);
331void to_value(bool b, google::protobuf::Value* v);
332void to_value(double d, google::protobuf::Value* v);
333void to_value(std::string s, google::protobuf::Value* v);
334void to_value(const ProtoList& vec, google::protobuf::Value* v);
335void to_value(const ProtoStruct& m, google::protobuf::Value* v);
336
337template <typename T>
338struct kind {};
339
340template <ProtoValue::Kind k>
341using KindConstant = std::integral_constant<ProtoValue::Kind, k>;
342
343template <>
344struct kind<std::nullptr_t> {
345 using type = KindConstant<ProtoValue::Kind::k_null>;
346};
347
348template <>
349struct kind<bool> {
350 using type = KindConstant<ProtoValue::Kind::k_bool>;
351};
352
353template <>
354struct kind<double> {
355 using type = KindConstant<ProtoValue::Kind::k_double>;
356};
357
358template <>
359struct kind<std::string> {
360 using type = KindConstant<ProtoValue::Kind::k_string>;
361};
362
363template <>
364struct kind<ProtoList> {
365 using type = KindConstant<ProtoValue::Kind::k_list>;
366};
367
368template <>
369struct kind<ProtoStruct> {
370 using type = KindConstant<ProtoValue::Kind::k_struct>;
371};
372
373} // namespace proto_value_details
374
375template <typename T>
379
380template <typename T>
382 if (this->is_a<T>()) {
383 return this->self_.template get<T>();
384 }
385
386 return nullptr;
387}
388
389template <typename T>
390T const* ProtoValue::get() const {
391 if (this->is_a<T>()) {
392 return this->self_.template get<T>();
393 }
394
395 return nullptr;
396}
397
398} // namespace sdk
399} // 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.
const T & get_unchecked() const &
Return an immutable reference to the underlying T, without checking.
T * get()
Return a T pointer if this is_a<T>(), else return nullptr.
Definition proto_value.hpp:381
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:376
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.
T & get_unchecked() &
Return a reference to the underlying T, without checking.
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:338