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
51 public:
53 ProtoValue() noexcept;
54
56 template <typename T>
57 ProtoValue(T t) noexcept(std::is_nothrow_move_constructible<T>{});
58
60 ProtoValue(const char* str);
61
65
66 ProtoValue(const ProtoValue& other);
67
71
72 ProtoValue& operator=(const ProtoValue& other);
73
75
79 friend bool operator==(const ProtoValue& lhs, const ProtoValue& rhs);
80
81 void swap(ProtoValue& other) noexcept(proto_value_details::all_moves_noexcept{});
82
87 template <typename Value = google::protobuf::Value>
88 static ProtoValue from_proto(const Value& v); // NOLINT(misc-no-recursion)
89
90 friend void to_proto(const ProtoValue& t, google::protobuf::Value* v);
91
94
96 int kind() const;
97
99 template <typename T>
100 bool is_a() const;
101
103 bool is_null() const;
104
106 template <typename T>
107 T* get();
108
110 template <typename T>
111 T const* get() const;
112
114
115 private:
116 // This struct is our implementation of a virtual table, similar to what is created by the
117 // compiler for polymorphic types. We can't use actual polymorphic types because the
118 // implementation uses aligned stack storage, so we DIY it insted.
119 // The vtable can be thought of as defining a concept or interface which our ProtoValue types
120 // must satisfy.
121 // The first void [const]* parameter in any of the pointers below is always the `this` or `self`
122 // pointer.
123 struct vtable {
124 void (*dtor)(void*);
125 void (*copy)(void const*, void*);
126 void (*move)(void*, void*);
127 void (*to_proto)(void const*, google::protobuf::Value*);
128 int (*kind)();
129 bool (*equal_to)(void const*, void const*, const vtable&);
130 };
131
132 // Class template for a concrete model of the concept described by vtable.
133 // All construction and equality operations are implemented using defaults of the stored T.
134 template <typename T>
135 struct model {
136 static_assert(std::is_nothrow_destructible<T>{}, "T has a throwing destructor");
137
138 model(T t) noexcept(std::is_nothrow_move_constructible<T>{});
139
140 static void dtor(void* self) noexcept;
141
142 static void copy(void const* self, void* dest);
143
144 // Clang bug prevents us from marking this noexcept but it's being called through a
145 // non-noexcept pointer anyway
146 static void move(void* self, void* dest);
147
148 static void to_proto(void const* self, google::protobuf::Value* v);
149
150 static int kind() noexcept;
151
152 static bool equal_to(void const* self, void const* other, const vtable& other_vtable);
153
154 static constexpr vtable vtable_{dtor, copy, move, to_proto, kind, equal_to};
155 T data;
156 };
157
158 // Stack-based storage for instances of model<T>.
159 // This is an RAII class which manages lifetime, pointer access, and construction of a
160 // type-erased model<T>. Note that all lifetime operations require a vtable to which the
161 // implementation defers.
162 struct storage {
163 // Local storage in an aligned_union which can hold all the possible ProtoValue types, using
164 // stand-ins for std::vector<ProtoValue> and ProtoStruct which are not available until end
165 // of class definition.
166 using BufType = std::aligned_union_t<0,
167 std::nullptr_t,
168 bool,
169 int,
170 double,
171 std::string,
172 std::vector<void*>,
173 std::unordered_map<std::string, void*>>;
174
175 static constexpr std::size_t local_storage_size = sizeof(BufType);
176 static constexpr std::size_t local_storage_alignment = alignof(BufType);
177
178 // Construct this to store a model<T>.
179 template <typename T>
180 storage(T t) noexcept(std::is_nothrow_move_constructible<T>{});
181
182 // These default special member functions are deleted in favor of overloads below which take
183 // a vtable parameter.
184 // We need the vtable because otherwise we have no way to perform the requisite copy, move,
185 // or destructor operations.
186 storage(const storage&) = delete;
187 storage(storage&&) = delete;
188 storage& operator=(const storage&) = delete;
189 storage& operator=(storage&&) = delete;
190
191 // Copy construct this storage from other, using the copy operation in vtable.
192 storage(const storage& other, const vtable& vtable);
193
194 // Move construct this storage from other, using the move operation in vtable.
195 storage(storage&& other,
196 const vtable& vtable) noexcept(proto_value_details::all_moves_noexcept{});
197
198 // Swap this storage with other, with operations on this provided by this_vtable, and
199 // operations on other provided by other_vtable.
200 void swap(const vtable& this_vtable,
201 storage& other,
202 const vtable& other_vtable) noexcept(proto_value_details::all_moves_noexcept{});
203
204 // Destroy this storage, using the destructor from vtable.
205 void destruct(const vtable& vtable) noexcept;
206
207 template <typename T = void>
208 T* get() {
209 return static_cast<T*>(static_cast<void*>(&buf_));
210 }
211
212 template <typename T = void>
213 T const* get() const {
214 return static_cast<T const*>(static_cast<void const*>(&buf_));
215 }
216
217 BufType buf_;
218 };
219
220 ProtoValue(const google::protobuf::Value* value);
221
222 vtable vtable_;
223 storage self_;
224};
225
226// Pre c++17 this is still required
227template <typename T>
228constexpr ProtoValue::vtable ProtoValue::model<T>::vtable_;
229
233using ProtoStruct = std::unordered_map<std::string, ProtoValue>;
234
235void to_proto(std::nullptr_t, google::protobuf::Value* v);
236void to_proto(bool b, google::protobuf::Value* v);
237void to_proto(int i, google::protobuf::Value* v);
238void to_proto(double d, google::protobuf::Value* v);
239void to_proto(std::string s, google::protobuf::Value* v);
240void to_proto(const std::vector<ProtoValue>& vec, google::protobuf::Value* v);
241void to_proto(const ProtoStruct& m, google::protobuf::Value* v);
242void to_proto(const ProtoValue& t, google::protobuf::Value* v);
243
244ProtoStruct struct_to_map(google::protobuf::Struct const* s);
245void map_to_struct(const ProtoStruct& m, google::protobuf::Struct* s);
246
251template <typename T, typename Value = google::protobuf::Value>
252Value to_proto(T&& t) {
253 Value v;
254 to_proto(std::forward<T>(t), &v);
255
256 return v;
257}
258
263template <typename Struct = google::protobuf::Struct>
264ProtoStruct struct_to_map(const Struct& s) {
265 return struct_to_map(&s);
266}
267
272template <typename Struct = google::protobuf::Struct>
273Struct map_to_struct(const ProtoStruct& m) {
274 Struct s;
275 map_to_struct(m, &s);
276
277 return s;
278}
279
284template <typename T>
285struct kind_t;
286
287template <>
288struct kind_t<std::nullptr_t> : std::integral_constant<int, 0> {};
289
290template <>
291struct kind_t<bool> : std::integral_constant<int, 1> {};
292
293template <>
294struct kind_t<int> : std::integral_constant<int, 2> {};
295
296template <>
297struct kind_t<double> : std::integral_constant<int, 3> {};
298
299template <>
300struct kind_t<std::string> : std::integral_constant<int, 4> {};
301
302template <>
303struct kind_t<std::vector<ProtoValue>> : std::integral_constant<int, 5> {};
304
305template <>
306struct kind_t<ProtoStruct> : std::integral_constant<int, 6> {};
307
308template <typename T>
309bool ProtoValue::is_a() const {
310 return kind_t<T>::value == kind();
311}
312
313template <typename T>
315 if (this->is_a<T>()) {
316 return this->self_.template get<T>();
317 }
318
319 return nullptr;
320}
321
322template <typename T>
323T const* ProtoValue::get() const {
324 if (this->is_a<T>()) {
325 return this->self_.template get<T>();
326 }
327
328 return nullptr;
329}
330
331} // namespace sdk
332} // namespace viam
Type-erased value for storing google::protobuf::Value types. A ProtoValue can be nullptr,...
Definition proto_value.hpp:50
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.
ProtoValue(const char *str)
Deduction helper constructor for string from string literal.
T * get()
Return a T pointer if this is_a<T>(), else return nullptr.
Definition proto_value.hpp:314
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:309
friend bool operator==(const ProtoValue &lhs, const ProtoValue &rhs)
Test equality of two types.
int kind() const
Obtain integer constant representing the stored data type.
T const * get() const
Return a T pointer if this is_a<T>(), else return nullptr.
ProtoValue RTTI type trait. This type trait is used to implement the ProtoValue::kind method which pr...
Definition proto_value.hpp:285