parcel 0.2.2
Wrappable, wire-transferable C++23 value system with JSON serialization
Loading...
Searching...
No Matches
union.h
Go to the documentation of this file.
1#pragma once
2
14#include <parcel/cell.h>
15#include <parcel/descriptor.h>
16
17#include <array>
18#include <cstddef>
19#include <memory>
20#include <stdexcept>
21#include <string>
22#include <string_view>
23#include <tuple>
24#include <type_traits>
25#include <utility>
26#include <variant>
27
28namespace parcel {
29class ParcelRegistry;
30
31template <CellLike... Ts>
32class UnionCell;
33
42template <CellLike... Ts>
43class UnionCellTypeDescriptor final : public BaseCellTypeDescriptor<UnionCell<Ts...>>,
44 public ISubTypes {
45public:
46 using cell_type = UnionCell<Ts...>;
48
53 explicit UnionCellTypeDescriptor(DisplayInfo info) : base_t(std::move(info)) {}
54
56 [[nodiscard]] static constexpr std::array<std::string_view, sizeof...(Ts)> alternatives() {
57 return {Ts::kind_id...};
58 }
59
60 [[nodiscard]] descriptor::CellCategory category() const override {
61 return descriptor::CellCategory::Union;
62 }
63
64 [[nodiscard]] std::vector<std::string_view> sub_kinds() const override {
65 return {Ts::kind_id...};
66 }
67
68 [[nodiscard]] cell_t cell_from_json(json_t const& j, ParcelRegistry const& reg) const override {
69 return UnionCell<Ts...>::from_json(j, reg);
70 }
71
72 [[nodiscard]] json_t to_json() const override {
73 auto j = base_t::base_to_json();
74 json_t arr = json_t::array();
75 ((arr.push_back(Ts::kind_id)), ...);
76 j["alternatives"] = std::move(arr);
77 return j;
78 }
79};
80
99template <CellLike... Ts>
100class UnionCell : public BaseCell<UnionCell<Ts...>, std::variant<typename Ts::storage_t...>> {
101 using base_t = BaseCell<UnionCell<Ts...>, std::variant<typename Ts::storage_t...>>;
102
103 static_assert(sizeof...(Ts) > 0, "UnionCell requires at least one alternative");
104
105public:
106 using base_t::base_t;
107 using base_t::operator=;
108
110 using variant_storage_t = std::variant<typename Ts::storage_t...>;
111
113 template <std::size_t I>
114 using nth_wrapper_t = std::tuple_element_t<I, std::tuple<Ts...>>;
115
117 static constexpr std::size_t alternative_count = sizeof...(Ts);
118
120 static constexpr std::array<std::string_view, sizeof...(Ts)> alternatives{Ts::kind_id...};
121
122 // kind_id encodes every alternative kind in template order so that each
123 // distinct UnionCell<Ts...> instantiation is its own registry kind:
124 // UnionCell<I32Cell, StringCell>::kind_id == "u:i32,string"
125 // UnionCell<I32Cell, TypedListCell<I32Cell>>::kind_id == "u:i32,l:i32"
126private:
127 static constexpr auto kind_id_buf = [] {
128 constexpr std::array<std::string_view, sizeof...(Ts)> alts{Ts::kind_id...};
129 constexpr std::size_t total = [&] {
130 std::size_t n = 2 + (sizeof...(Ts) - 1);
131 for (auto sv : alts) {
132 n += sv.size();
133 }
134 return n;
135 }();
136 std::array<char, total> buf{};
137 std::size_t pos = 0;
138 buf[pos++] = 'u';
139 buf[pos++] = ':';
140 for (std::size_t i = 0; i < alts.size(); ++i) {
141 if (i > 0) {
142 buf[pos++] = ',';
143 }
144 for (char c : alts[i]) {
145 buf[pos++] = c;
146 }
147 }
148 return buf;
149 }();
150
151public:
159 static constexpr std::string_view kind_id{kind_id_buf.data(), kind_id_buf.size()};
160
161 // ---- variant-style accessors -----------------------------------------
162
164 [[nodiscard]] std::size_t index() const noexcept {
165 return this->value.index();
166 }
167
173 template <std::size_t I>
174 [[nodiscard]] auto& get() {
175 return std::get<I>(this->value);
176 }
177
183 template <std::size_t I>
184 [[nodiscard]] auto const& get() const {
185 return std::get<I>(this->value);
186 }
187
193 template <typename S>
194 [[nodiscard]] S& get() {
195 return std::get<S>(this->value);
196 }
197
203 template <typename S>
204 [[nodiscard]] S const& get() const {
205 return std::get<S>(this->value);
206 }
207
209 [[nodiscard]] std::string_view active_kind() const {
210 return alternatives[this->value.index()];
211 }
212
213 [[nodiscard]] std::string to_string() const override {
214 return visit_indexed([]<std::size_t I>(std::integral_constant<std::size_t, I>,
215 auto const& storage) -> std::string {
216 return nth_wrapper_t<I>(storage).to_string();
217 });
218 }
219
220 [[nodiscard]] json_t to_json() const override {
221 json_t j = visit_indexed([]<std::size_t I>(std::integral_constant<std::size_t, I>,
222 auto const& storage) -> json_t {
223 using WrapperT = nth_wrapper_t<I>;
224 WrapperT wrapper(storage);
225 return json_t{
227 {ICell::KEY_VALUE, wrapper.to_json()},
228 };
229 });
230 this->inject_display_info(j);
231 return j;
232 }
233
234 [[nodiscard]] cell_t clone() const override {
235 auto out = std::make_shared<UnionCell<Ts...>>();
236 visit_indexed(
237 [&out]<std::size_t I>(std::integral_constant<std::size_t, I>, auto const& storage) {
238 using WrapperT = nth_wrapper_t<I>;
239 const cell_t cloned = WrapperT(storage).clone();
240 auto* typed = dynamic_cast<WrapperT*>(cloned.get());
241 if (typed == nullptr) {
242 throw std::runtime_error("UnionCell::clone: alternative type mismatch");
243 }
244 out->value.template emplace<I>(std::move(typed->value));
245 });
246 out->display_info_ = this->display_info_;
247 return out;
248 }
249
258 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
259 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
260 return k_cmp;
261 }
262 const auto* o = dynamic_cast<UnionCell const*>(&other);
263 if (o == nullptr) {
264 return std::partial_ordering::unordered;
265 }
266 if (const auto idx_cmp = this->value.index() <=> o->value.index(); idx_cmp != 0) {
267 return idx_cmp;
268 }
269 return compare_active(*o, std::index_sequence_for<Ts...>{});
270 }
271
284 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
285 if (!j.is_object()) {
286 throw InvalidJsonException("Expected JSON object for UnionCell", std::string(kind_id));
287 }
288 const auto it_k = j.find(ICell::KEY_KIND);
289 if (it_k == j.end() || !it_k->is_string()) {
290 throw InvalidJsonException("UnionCell: missing/invalid 'k' (expected '" +
291 std::string(kind_id) + "')",
292 std::string(kind_id));
293 }
294 if (it_k->get<std::string_view>() != kind_id) {
295 throw KindMismatchException("UnionCell: missing/invalid 'k' (expected '" +
296 std::string(kind_id) + "')",
297 std::string(kind_id));
298 }
299 const auto it_v = j.find(ICell::KEY_VALUE);
300 if (it_v == j.end() || !it_v->is_object()) {
301 throw InvalidJsonException("UnionCell: missing/invalid 'v' (expected object)",
302 std::string(kind_id));
303 }
304 const auto it_inner_k = it_v->find(ICell::KEY_KIND);
305 if (it_inner_k == it_v->end() || !it_inner_k->is_string()) {
306 throw InvalidJsonException("UnionCell: inner 'v' missing/invalid 'k'",
307 std::string(kind_id));
308 }
309 const auto active = it_inner_k->get<std::string_view>();
310 cell_t out = dispatch_from_json(active, *it_v, reg, std::index_sequence_for<Ts...>{});
312 return out;
313 }
314
320 static const auto d = std::make_shared<UnionCellTypeDescriptor<Ts...>>(DisplayInfo{
321 .name = "Union",
322 .description = "Closed-set polymorphic cell (one of fixed alternatives)",
323 });
324 return d;
325 }
326
342 template <typename F>
343 decltype(auto) visit(F&& f) {
344 return std::visit(std::forward<F>(f), this->value);
345 }
346
348 template <typename F>
349 decltype(auto) visit(F&& f) const {
350 return std::visit(std::forward<F>(f), this->value);
351 }
352
357 template <std::size_t I>
358 [[nodiscard]] auto* get_if() noexcept {
359 return std::get_if<I>(&this->value);
360 }
361
363 template <std::size_t I>
364 [[nodiscard]] auto const* get_if() const noexcept {
365 return std::get_if<I>(&this->value);
366 }
367
372 template <typename S>
373 [[nodiscard]] S* get_if() noexcept {
374 return std::get_if<S>(&this->value);
375 }
376
378 template <typename S>
379 [[nodiscard]] S const* get_if() const noexcept {
380 return std::get_if<S>(&this->value);
381 }
382
384 template <std::size_t I>
385 [[nodiscard]] bool holds_alternative() const noexcept {
386 return this->value.index() == I;
387 }
388
390 template <typename S>
391 [[nodiscard]] bool holds() const noexcept {
392 return std::holds_alternative<S>(this->value);
393 }
394
395private:
396 template <typename F>
397 auto visit_indexed(F&& f) const {
398 return visit_indexed_impl(std::forward<F>(f), std::index_sequence_for<Ts...>{});
399 }
400
401 template <typename F, std::size_t... Is>
402 auto visit_indexed_impl(F&& f, std::index_sequence<Is...>) const {
403 using R = std::invoke_result_t<F&,
404 std::integral_constant<std::size_t, 0>,
405 typename nth_wrapper_t<0>::storage_t const&>;
406 if constexpr (std::is_void_v<R>) {
407 (void)((this->value.index() == Is
408 ? (f(std::integral_constant<std::size_t, Is>{}, std::get<Is>(this->value)),
409 true)
410 : false) ||
411 ...);
412 } else {
413 R result{};
414 const bool matched =
415 ((this->value.index() == Is ? (result = f(std::integral_constant<std::size_t, Is>{},
416 std::get<Is>(this->value)),
417 true)
418 : false) ||
419 ...);
420 if (!matched) {
421 throw std::runtime_error("UnionCell: valueless variant");
422 }
423 return result;
424 }
425 }
426
427 template <std::size_t... Is>
428 [[nodiscard]] std::partial_ordering compare_active(UnionCell const& other,
429 std::index_sequence<Is...>) const {
430 std::partial_ordering result = std::partial_ordering::equivalent;
431 const auto idx = this->value.index();
432 const bool matched = ((idx == Is ? (result = compare_alt<Is>(other), true) : false) || ...);
433 (void)matched;
434 return result;
435 }
436
437 template <std::size_t I>
438 [[nodiscard]] std::partial_ordering compare_alt(UnionCell const& other) const {
439 using WrapperT = nth_wrapper_t<I>;
440 WrapperT a(std::get<I>(this->value));
441 WrapperT b(std::get<I>(other.value));
442 return a.compare(b);
443 }
444
445 template <std::size_t... Is>
446 static cell_t dispatch_from_json(const std::string_view active,
447 json_t const& raw,
448 ParcelRegistry const& reg,
449 std::index_sequence<Is...>) {
450 cell_t out;
451 if (const bool matched = ((try_alt<Is>(active, raw, reg, out)) || ...); !matched) {
452 throw KindMismatchException("UnionCell: active kind '" + std::string(active) +
453 "' is not one of the alternatives",
454 std::string(kind_id));
455 }
456 return out;
457 }
458
459 template <std::size_t I>
460 static bool
461 try_alt(std::string_view active, json_t const& raw, ParcelRegistry const& reg, cell_t& out) {
462 using NthT = nth_wrapper_t<I>;
463 if (active != NthT::kind_id) {
464 return false;
465 }
466 const cell_t v = NthT::from_json(raw, reg);
467 auto* typed = dynamic_cast<NthT*>(v.get());
468 if (typed == nullptr) {
469 throw KindMismatchException(
470 "UnionCell: alternative from_json did not return expected type",
471 std::string(kind_id));
472 }
473 auto u = std::make_shared<UnionCell<Ts...>>();
474 u->value.template emplace<I>(std::move(typed->value));
475 out = u;
476 return true;
477 }
478};
479
486template <typename F, CellLike... Ts>
487decltype(auto) visit(F&& f, UnionCell<Ts...>& u) {
488 return u.visit(std::forward<F>(f));
489}
490
492template <typename F, CellLike... Ts>
493decltype(auto) visit(F&& f, UnionCell<Ts...> const& u) {
494 return u.visit(std::forward<F>(f));
495}
496
500template <std::size_t I, CellLike... Ts>
502 return u.template get<I>();
503}
504
505template <std::size_t I, CellLike... Ts>
506auto const& get(UnionCell<Ts...> const& u) {
507 return u.template get<I>();
508}
509
513template <typename S, CellLike... Ts>
515 return u.template get<S>();
516}
517
518template <typename S, CellLike... Ts>
519S const& get(UnionCell<Ts...> const& u) {
520 return u.template get<S>();
521}
522
526template <std::size_t I, CellLike... Ts>
527auto* get_if(UnionCell<Ts...>* u) noexcept {
528 return u ? u->template get_if<I>() : nullptr;
529}
530
531template <std::size_t I, CellLike... Ts>
532auto const* get_if(UnionCell<Ts...> const* u) noexcept {
533 return u ? u->template get_if<I>() : nullptr;
534}
535
536template <typename S, CellLike... Ts>
537S* get_if(UnionCell<Ts...>* u) noexcept {
538 return u ? u->template get_if<S>() : nullptr;
539}
540
541template <typename S, CellLike... Ts>
542S const* get_if(UnionCell<Ts...> const* u) noexcept {
543 return u ? u->template get_if<S>() : nullptr;
544}
545
560template <class... Fs>
561struct Overload : Fs... {
562 using Fs::operator()...;
563};
564
565template <class... Fs>
566Overload(Fs...) -> Overload<Fs...>;
567
568} // namespace parcel
Core ICell interface, cell_t handle, BaseCell CRTP base, and CellLike concept.
std::shared_ptr< ICellTypeDescriptor > cell_type_descriptor_t
Shared handle to a runtime cell-type descriptor.
Definition cell.h:63
std::shared_ptr< ICell > cell_t
Shared handle to any ICell-derived value — the canonical cell pointer.
Definition cell.h:68
CRTP base that fills in the boilerplate of every cell-type descriptor.
Definition descriptor.h:165
json_t base_to_json() const
Common JSON skeleton (kind, display_info, category) reused by overrides.
Definition descriptor.h:202
JSON shape was wrong (missing/non-string k, missing v, etc.).
Definition error.h:158
k field was present but did not match the expected kind.
Definition error.h:168
Runtime catalog of cell-type descriptors, keyed by wire kind id.
Definition registry.h:115
Descriptor for UnionCell<Ts...>.
Definition union.h:44
json_t to_json() const override
Serialize the descriptor itself (kind + display info + category + extras).
Definition union.h:72
cell_t cell_from_json(json_t const &j, ParcelRegistry const &reg) const override
Construct a cell from JSON, dispatching to the concrete from_json.
Definition union.h:68
std::vector< std::string_view > sub_kinds() const override
Sub-kind ids reachable from this descriptor.
Definition union.h:64
UnionCellTypeDescriptor(DisplayInfo info)
Construct with the given display info.
Definition union.h:53
descriptor::CellCategory category() const override
Coarse classification (primitive, list, struct, …).
Definition union.h:60
static constexpr std::array< std::string_view, sizeof...(Ts)> alternatives()
Compile-time array of every alternative's kind id.
Definition union.h:56
Closed-set polymorphic cell — exactly one of Ts at runtime.
Definition union.h:100
static constexpr std::array< std::string_view, sizeof...(Ts)> alternatives
Kind id of each alternative in template order.
Definition union.h:120
auto & get()
Access the value at alternative I (mutable).
Definition union.h:174
S const * get_if() const noexcept
Const overload of get_if<S>.
Definition union.h:379
auto const * get_if() const noexcept
Const overload of get_if<I>.
Definition union.h:364
std::variant< typename Ts::storage_t... > variant_storage_t
Storage variant — one slot per alternative.
Definition union.h:110
S * get_if() noexcept
Returns a pointer to the active alternative if its storage is S, else nullptr — never throws.
Definition union.h:373
std::string_view active_kind() const
Kind id of the currently active alternative.
Definition union.h:209
std::partial_ordering compare(ICell const &other) const override
Three-way comparison: kind, then active alternative index, then the active alternative's wrapper-leve...
Definition union.h:258
bool holds() const noexcept
true if the active alternative's storage type is S.
Definition union.h:391
auto * get_if() noexcept
Returns a pointer to the active alternative if it lives at slot I, else nullptr — never throws.
Definition union.h:358
static cell_t from_json(json_t const &j, ParcelRegistry const &reg)
Deserialize a UnionCell<Ts...> from JSON.
Definition union.h:284
decltype(auto) visit(F &&f) const
Const overload of visit.
Definition union.h:349
static constexpr std::size_t alternative_count
Number of alternatives.
Definition union.h:117
static cell_type_descriptor_t descriptor()
Cached descriptor for this union.
Definition union.h:319
std::string to_string() const override
Render the cell's value as a compact human-readable string.
Definition union.h:213
bool holds_alternative() const noexcept
true if the active alternative lives at slot I.
Definition union.h:385
std::tuple_element_t< I, std::tuple< Ts... > > nth_wrapper_t
Wrapper type at the I -th alternative slot.
Definition union.h:114
std::size_t index() const noexcept
Index of the currently active alternative.
Definition union.h:164
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition union.h:220
S const & get() const
Access the value held in storage type S (const).
Definition union.h:204
S & get()
Access the value held in storage type S (mutable).
Definition union.h:194
auto const & get() const
Access the value at alternative I (const).
Definition union.h:184
cell_t clone() const override
Deep-copy this cell.
Definition union.h:234
static constexpr std::string_view kind_id
Wire kind id of this union ("u:" + alternatives joined by ",").
Definition union.h:159
decltype(auto) visit(F &&f)
Visit the active alternative with f, called as f(storage).
Definition union.h:343
comms::DisplayInfo DisplayInfo
Display info attached to a cell or descriptor — re-exported from comms::DisplayInfo.
Definition common.h:75
Runtime cell-type descriptors and the schema-graph mix-ins (IHasFields, ISubTypes).
CellCategory
Coarse runtime classification of a cell type.
Definition descriptor.h:35
nlohmann::json json_t
Project-wide alias for nlohmann::json.
Definition json.h:19
CRTP base providing default to_json / clone / kind plumbing on top of a storage type.
Definition cell.h:343
std::optional< DisplayInfo > display_info_
Optional display info; omitted from JSON when empty.
Definition cell.h:507
std::string_view kind() const override
Definition cell.h:411
static void absorb_display_info(json_t const &j, Out &out)
Read "d" (if present) from a JSON object and assign it onto a cell.
Definition cell.h:499
std::variant< Ts::storage_t... > value
Held value of the cell.
Definition cell.h:348
void inject_display_info(json_t &j) const
Copy this cell's display info (if any) into the JSON object under "d".
Definition cell.h:486
Polymorphic root of every parcel cell.
Definition cell.h:84
static constexpr std::string_view KEY_VALUE
JSON key for the value payload ("v").
Definition cell.h:88
virtual std::string_view kind() const =0
Wire-stable kind identifier for this cell.
static constexpr std::string_view KEY_KIND
JSON key for the kind id ("k").
Definition cell.h:86
Mix-in declaring a descriptor refers to other registered cell kinds.
Definition descriptor.h:148
Lambda-overload set helper for parcel::visit.
Definition union.h:561
auto & get(UnionCell< Ts... > &u)
Free get<I> over UnionCell<Ts...> mirroring std::get<I>(variant).
Definition union.h:501
auto * get_if(UnionCell< Ts... > *u) noexcept
Free get_if<I> over UnionCell<Ts...>; returns nullptr on miss.
Definition union.h:527
decltype(auto) visit(F &&f, UnionCell< Ts... > &u)
std::visit-style free function over UnionCell<Ts...>.
Definition union.h:487