parcel 0.2.2
Wrappable, wire-transferable C++23 value system with JSON serialization
Loading...
Searching...
No Matches
list.h
Go to the documentation of this file.
1#pragma once
2
15#include <parcel/cell.h>
16#include <parcel/descriptor.h>
17#include <parcel/registry.h>
18
19#include <initializer_list>
20#include <memory>
21#include <ranges>
22#include <span>
23#include <stdexcept>
24#include <string>
25#include <string_view>
26#include <utility>
27#include <vector>
28
29namespace parcel {
30
31// std::vector<T>::operator== eagerly instantiates T::operator==; suppress
32// BaseCell's default compare for vector storage so cells whose element
33// payloads lack `==` still compile (TypedListCell overrides compare()).
34template <typename T, typename A>
35struct detail::disables_basecell_compare<std::vector<T, A>> : std::true_type {
36}; // namespace detail
37
38class ParcelRegistry;
39
40template <CellLike T>
41class TypedListCell;
42
52template <CellLike T>
53class TypedListCellTypeDescriptor final : public BaseCellTypeDescriptor<TypedListCell<T>>,
54 public ISubTypes {
55public:
58
63 explicit TypedListCellTypeDescriptor(DisplayInfo info) : base_t(std::move(info)) {}
64
66 [[nodiscard]] std::string_view element_kind() const {
67 return T::kind_id;
68 }
69
70 [[nodiscard]] descriptor::CellCategory category() const override {
71 return descriptor::CellCategory::TypedList;
72 }
73
74 [[nodiscard]] std::vector<std::string_view> sub_kinds() const override {
75 return {T::kind_id};
76 }
77
78 [[nodiscard]] cell_t cell_from_json(json_t const& j, ParcelRegistry const& reg) const override {
79 return TypedListCell<T>::from_json(j, reg);
80 }
81
82 [[nodiscard]] json_t to_json() const override {
83 auto j = base_t::base_to_json();
84 j["element_kind"] = T::kind_id;
85 return j;
86 }
87};
88
105template <CellLike T>
106class TypedListCell : public BaseCell<TypedListCell<T>, std::vector<typename T::storage_t>> {
108
109public:
110 using element_type = T;
111 using element_storage_t = typename T::storage_t;
112
113 using base_t::base_t;
114 using base_t::operator=;
115
120 TypedListCell(std::initializer_list<element_storage_t> elems)
121 : base_t(std::vector<element_storage_t>(elems)) {}
122
129 template <std::ranges::input_range R>
130 requires std::convertible_to<std::ranges::range_value_t<R>, element_storage_t>
131 TypedListCell(std::from_range_t /*tag*/, R&& r)
132 : base_t(std::vector<element_storage_t>(std::ranges::begin(r), std::ranges::end(r))) {}
133
137 explicit TypedListCell(std::span<const element_storage_t> sp)
138 : base_t(std::vector<element_storage_t>(sp.begin(), sp.end())) {}
139
140 // `as_span` is disabled for `bool` because `std::vector<bool>` is the
141 // packed bitset specialization — it has no `data()` and cannot be viewed
142 // as a contiguous range of `bool`. Reach for `value` (the vector itself)
143 // and iterate, or pick a different element cell for span-based access.
144
146 [[nodiscard]] std::span<const element_storage_t> as_span() const noexcept
147 requires(!std::same_as<element_storage_t, bool>)
148 {
149 return std::span<const element_storage_t>(this->value.data(), this->value.size());
150 }
151
153 [[nodiscard]] std::span<element_storage_t> as_span() noexcept
154 requires(!std::same_as<element_storage_t, bool>)
155 {
156 return std::span<element_storage_t>(this->value.data(), this->value.size());
157 }
158
159 // ---- std::vector pass-through API ------------------------------------
160 // Forwards a curated subset of std::vector<element_storage_t>'s API so
161 // TypedListCell can be used directly without going through .value /
162 // .get(). The list is transparent: elements are raw element_storage_t
163 // values, not ICell wrappers. The kind discipline lives on the type tag.
164
165 using vec_t = std::vector<element_storage_t>;
166 using value_type = vec_t::value_type;
167 using size_type = vec_t::size_type;
168 using iterator = vec_t::iterator;
169 using const_iterator = vec_t::const_iterator;
170 using reference = vec_t::reference;
171 using const_reference = vec_t::const_reference;
172
173 [[nodiscard]] size_type size() const noexcept {
174 return this->value.size();
175 }
176 [[nodiscard]] bool empty() const noexcept {
177 return this->value.empty();
178 }
179 void reserve(size_type n) {
180 this->value.reserve(n);
181 }
182
183 reference operator[](size_type i) {
184 return this->value[i];
185 }
186 const_reference operator[](size_type i) const {
187 return this->value[i];
188 }
189 reference at(size_type i) {
190 return this->value.at(i);
191 }
192 const_reference at(size_type i) const {
193 return this->value.at(i);
194 }
195 reference front() {
196 return this->value.front();
197 }
198 [[nodiscard]] const_reference front() const {
199 return this->value.front();
200 }
201 reference back() {
202 return this->value.back();
203 }
204 [[nodiscard]] const_reference back() const {
205 return this->value.back();
206 }
207
208 iterator begin() noexcept {
209 return this->value.begin();
210 }
211 [[nodiscard]] const_iterator begin() const noexcept {
212 return this->value.begin();
213 }
214 [[nodiscard]] const_iterator cbegin() const noexcept {
215 return this->value.cbegin();
216 }
217 iterator end() noexcept {
218 return this->value.end();
219 }
220 [[nodiscard]] const_iterator end() const noexcept {
221 return this->value.end();
222 }
223 [[nodiscard]] const_iterator cend() const noexcept {
224 return this->value.cend();
225 }
226
227 void push_back(element_storage_t const& v) {
228 this->value.push_back(v);
229 }
230 void push_back(element_storage_t&& v) {
231 this->value.push_back(std::move(v));
232 }
233 template <typename... Args>
234 reference emplace_back(Args&&... args) {
235 return this->value.emplace_back(std::forward<Args>(args)...);
236 }
237 void pop_back() {
238 this->value.pop_back();
239 }
240 void clear() noexcept {
241 this->value.clear();
242 }
243 iterator erase(const_iterator pos) {
244 return this->value.erase(pos);
245 }
246 iterator erase(const_iterator first, const_iterator last) {
247 return this->value.erase(first, last);
248 }
249 iterator insert(const_iterator pos, element_storage_t const& v) {
250 return this->value.insert(pos, v);
251 }
252 iterator insert(const_iterator pos, element_storage_t&& v) {
253 return this->value.insert(pos, std::move(v));
254 }
255 // ----------------------------------------------------------------------
256
267 static constexpr std::string_view kind_id = prefixed_id_v<"l:", T::kind_id>;
268
273 [[nodiscard]] std::string to_string() const override {
274 std::string out = "[";
275 bool first = true;
276 for (auto const& el : this->value) {
277 if (!first) {
278 out += ", ";
279 }
280 T wrapper(el);
281 out += wrapper.to_string();
282 first = false;
283 }
284 out += "]";
285 return out;
286 }
287
288 [[nodiscard]] json_t to_json() const override {
289 json_t arr = json_t::array();
290 for (auto const& el : this->value) {
291 T wrapper(el);
292 arr.push_back(wrapper.to_json().at(ICell::KEY_VALUE));
293 }
294 json_t j{
296 {ICell::KEY_VALUE, std::move(arr)},
297 };
298 this->inject_display_info(j);
299 return j;
300 }
301
309 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
310 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
311 return k_cmp;
312 }
313 const auto* o = dynamic_cast<TypedListCell const*>(&other);
314 if (o == nullptr) {
315 return std::partial_ordering::unordered;
316 }
317 const auto& a = this->value;
318 const auto& b = o->value;
319 const std::size_t n = std::min(a.size(), b.size());
320 for (std::size_t i = 0; i < n; ++i) {
321 T wa(a[i]);
322 T wb(b[i]);
323 if (const auto c = wa.compare(wb); c != 0) {
324 return c;
325 }
326 }
327 return a.size() <=> b.size();
328 }
329
338 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
339 if (!j.is_object()) {
340 throw InvalidJsonException("Expected JSON object for TypedListCell",
341 std::string(kind_id));
342 }
343
344 const auto it_k = j.find(ICell::KEY_KIND);
345 if (it_k == j.end() || !it_k->is_string()) {
346 throw InvalidJsonException("TypedListCell: missing/invalid 'k' (expected '" +
347 std::string(kind_id) + "')",
348 std::string(kind_id));
349 }
350 if (it_k->get<std::string_view>() != kind_id) {
351 throw KindMismatchException("TypedListCell: missing/invalid 'k' (expected '" +
352 std::string(kind_id) + "')",
353 std::string(kind_id));
354 }
355
356 const auto it_v = j.find(ICell::KEY_VALUE);
357 if (it_v == j.end() || !it_v->is_array()) {
358 throw InvalidJsonException("TypedListCell: missing/invalid 'v' (expected array)",
359 std::string(kind_id));
360 }
361
362 std::vector<element_storage_t> elems;
363 elems.reserve(it_v->size());
364 for (auto const& raw : *it_v) {
365 json_t wrapped{{ICell::KEY_KIND, T::kind_id}, {ICell::KEY_VALUE, raw}};
366 cell_t v = T::from_json(wrapped, reg);
367 auto* typed = dynamic_cast<T*>(v.get());
368 if (typed == nullptr) {
369 throw TypeException(
370 std::string("TypedListCell: element from_json did not return ") +
371 std::string(T::kind_id),
372 std::string(kind_id));
373 }
374 elems.push_back(std::move(typed->value));
375 }
376 auto cell = std::make_shared<TypedListCell>(std::move(elems));
378 return cell;
379 }
380
386 static const auto d = std::make_shared<TypedListCellTypeDescriptor<T>>(DisplayInfo{
387 .name = "List",
388 .description = std::string("Homogeneous list of ") + std::string(T::kind_id),
389 });
390 return d;
391 }
392};
393
408class ListCell : public BaseCell<ListCell, std::vector<cell_t>> {
409 using base_t = BaseCell;
410
411public:
412 using base_t::base_t;
413 using base_t::operator=;
414
419 ListCell(const std::initializer_list<cell_t> elems) : base_t(std::vector(elems)) {}
420
421 // ---- std::vector pass-through API ------------------------------------
422
423 using vec_t = std::vector<cell_t>;
424 using value_type = vec_t::value_type;
425 using size_type = vec_t::size_type;
426 using iterator = vec_t::iterator;
427 using const_iterator = vec_t::const_iterator;
428 using reference = vec_t::reference;
429 using const_reference = vec_t::const_reference;
430
431 [[nodiscard]] size_type size() const noexcept {
432 return this->value.size();
433 }
434 [[nodiscard]] bool empty() const noexcept {
435 return this->value.empty();
436 }
437 void reserve(const size_type n) {
438 this->value.reserve(n);
439 }
440
441 reference operator[](const size_type i) {
442 return this->value[i];
443 }
444 const_reference operator[](const size_type i) const {
445 return this->value[i];
446 }
447 reference at(const size_type i) {
448 return this->value.at(i);
449 }
450 [[nodiscard]] const_reference at(const size_type i) const {
451 return this->value.at(i);
452 }
453 reference front() {
454 return this->value.front();
455 }
456 [[nodiscard]] const_reference front() const {
457 return this->value.front();
458 }
459 reference back() {
460 return this->value.back();
461 }
462 [[nodiscard]] const_reference back() const {
463 return this->value.back();
464 }
465
466 iterator begin() noexcept {
467 return this->value.begin();
468 }
469 [[nodiscard]] const_iterator begin() const noexcept {
470 return this->value.begin();
471 }
472 [[nodiscard]] const_iterator cbegin() const noexcept {
473 return this->value.cbegin();
474 }
475 iterator end() noexcept {
476 return this->value.end();
477 }
478 [[nodiscard]] const_iterator end() const noexcept {
479 return this->value.end();
480 }
481 [[nodiscard]] const_iterator cend() const noexcept {
482 return this->value.cend();
483 }
484
485 void push_back(cell_t const& v) {
486 this->value.push_back(v);
487 }
488 void push_back(cell_t&& v) {
489 this->value.push_back(std::move(v));
490 }
491 template <typename... Args>
492 reference emplace_back(Args&&... args) {
493 return this->value.emplace_back(std::forward<Args>(args)...);
494 }
495 void pop_back() {
496 this->value.pop_back();
497 }
498 void clear() noexcept {
499 this->value.clear();
500 }
501 iterator erase(const const_iterator pos) {
502 return this->value.erase(pos);
503 }
504 iterator erase(const const_iterator first, const const_iterator last) {
505 return this->value.erase(first, last);
506 }
507 iterator insert(const const_iterator pos, cell_t const& v) {
508 return this->value.insert(pos, v);
509 }
510 iterator insert(const const_iterator pos, cell_t&& v) {
511 return this->value.insert(pos, std::move(v));
512 }
513 // ----------------------------------------------------------------------
514
516 static constexpr std::string_view kind_id = "l";
517
522 [[nodiscard]] std::string to_string() const override {
523 std::string out = "[";
524 bool first = true;
525 for (auto const& el : this->value) {
526 if (!first) {
527 out += ", ";
528 }
529 out += el ? el->to_string() : "<null>";
530 first = false;
531 }
532 out += "]";
533 return out;
534 }
535
536 [[nodiscard]] json_t to_json() const override {
537 json_t arr = json_t::array();
538 for (auto const& el : this->value) {
539 arr.push_back(el ? el->to_json() : json_t());
540 }
541 json_t j{
542 {KEY_KIND, kind_id},
543 {KEY_VALUE, std::move(arr)},
544 };
545 this->inject_display_info(j);
546 return j;
547 }
548
549 [[nodiscard]] cell_t clone() const override {
550 std::vector<cell_t> copied;
551 copied.reserve(this->value.size());
552 for (auto const& el : this->value) {
553 copied.push_back(el ? el->clone() : nullptr);
554 }
555 auto out = std::make_shared<ListCell>(std::move(copied));
556 out->display_info_ = this->display_info_;
557 return out;
558 }
559
560 [[nodiscard]] std::size_t hash_value() const noexcept override {
561 std::size_t h = std::hash<std::string_view>{}(kind());
562 for (auto const& el : this->value) {
563 const std::size_t hv = el ? el->hash_value() : std::size_t{0};
564 h ^= hv + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
565 }
566 return h;
567 }
568
575 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
576 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
577 return k_cmp;
578 }
579 const auto* o = dynamic_cast<ListCell const*>(&other);
580 if (o == nullptr) {
581 return std::partial_ordering::unordered;
582 }
583 const auto& a = this->value;
584 const auto& b = o->value;
585 const std::size_t n = std::min(a.size(), b.size());
586 for (std::size_t i = 0; i < n; ++i) {
587 if (!a[i] && !b[i]) {
588 continue;
589 }
590 if (!a[i]) {
591 return std::partial_ordering::less;
592 }
593 if (!b[i]) {
594 return std::partial_ordering::greater;
595 }
596 if (const auto c = a[i]->compare(*b[i]); c != 0) {
597 return c;
598 }
599 }
600 return a.size() <=> b.size();
601 }
602
610 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
611 if (!j.is_object()) {
612 throw InvalidJsonException("Expected JSON object for ListCell", std::string(kind_id));
613 }
614
615 const auto it_k = j.find(KEY_KIND);
616 if (it_k == j.end() || !it_k->is_string()) {
617 throw InvalidJsonException("ListCell: missing/invalid 'k' (expected 'l')",
618 std::string(kind_id));
619 }
620 if (it_k->get<std::string_view>() != kind_id) {
621 throw KindMismatchException("ListCell: missing/invalid 'k' (expected 'l')",
622 std::string(kind_id));
623 }
624
625 const auto it_v = j.find(KEY_VALUE);
626 if (it_v == j.end() || !it_v->is_array()) {
627 throw InvalidJsonException("ListCell: missing/invalid 'v' (expected array)",
628 std::string(kind_id));
629 }
630
631 std::vector<cell_t> elems;
632 elems.reserve(it_v->size());
633 for (auto const& raw : *it_v) {
634 // Symmetric with to_json: a null element on the wire round-trips
635 // to a null cell_t handle, not a deserialization error.
636 elems.push_back(raw.is_null() ? cell_t{} : reg.cell_from_json(raw));
637 }
638 auto cell = std::make_shared<ListCell>(std::move(elems));
640 return cell;
641 }
642
648};
649
656class ListCellTypeDescriptor final : public BaseCellTypeDescriptor<ListCell> {
657public:
658 using cell_type = ListCell;
660
661 explicit ListCellTypeDescriptor(DisplayInfo info) : base_t(std::move(info)) {}
662
663 [[nodiscard]] descriptor::CellCategory category() const override {
664 return descriptor::CellCategory::List;
665 }
666
667 [[nodiscard]] cell_t cell_from_json(const json_t& j, const ParcelRegistry& reg) const override {
668 return ListCell::from_json(j, reg);
669 }
670};
671
673 static const auto d = std::make_shared<ListCellTypeDescriptor>(
674 DisplayInfo{.name = "List", .description = "Heterogeneous list of cells"});
675 return d;
676}
677
678} // namespace parcel
679
680// `make_list` lives in <parcel/defaults.h> because it needs `parcel::cell()`
681// which is declared there. The function is ADL-discoverable because it lives
682// in `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
Descriptor for the heterogeneous ListCell.
Definition list.h:656
descriptor::CellCategory category() const override
Coarse classification (primitive, list, struct, …).
Definition list.h:663
cell_t cell_from_json(const json_t &j, const ParcelRegistry &reg) const override
Construct a cell from JSON, dispatching to the concrete from_json.
Definition list.h:667
Heterogeneous list of cell_t — wire kind "l".
Definition list.h:408
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition list.h:536
cell_t clone() const override
Deep-copy this cell.
Definition list.h:549
static constexpr std::string_view kind_id
Wire kind id ("l").
Definition list.h:516
std::size_t hash_value() const noexcept override
Equality-consistent hash that mirrors compare's display-info-insensitivity.
Definition list.h:560
ListCell(const std::initializer_list< cell_t > elems)
Construct from a brace-enclosed list of cells.
Definition list.h:419
static cell_type_descriptor_t descriptor()
Cached descriptor for ListCell.
Definition list.h:672
static cell_t from_json(json_t const &j, ParcelRegistry const &reg)
Deserialize a ListCell from JSON.
Definition list.h:610
std::partial_ordering compare(ICell const &other) const override
Lexicographic three-way comparison over elements; ignores display info.
Definition list.h:575
std::string to_string() const override
Render the list as [a, b, c]; null elements appear as <null>.
Definition list.h:522
Runtime catalog of cell-type descriptors, keyed by wire kind id.
Definition registry.h:115
cell_t cell_from_json(json_t const &j) const
Deserialize any registered cell from JSON, dispatching by "k".
Definition registry.h:266
Typed value (cast, struct field, etc.) failed conversion.
Definition error.h:190
Descriptor for TypedListCell<T>.
Definition list.h:54
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 list.h:78
TypedListCellTypeDescriptor(DisplayInfo info)
Construct with the given display info.
Definition list.h:63
std::vector< std::string_view > sub_kinds() const override
Sub-kind ids reachable from this descriptor.
Definition list.h:74
json_t to_json() const override
Serialize the descriptor itself (kind + display info + category + extras).
Definition list.h:82
descriptor::CellCategory category() const override
Coarse classification (primitive, list, struct, …).
Definition list.h:70
std::string_view element_kind() const
Wire kind id of the list's element type.
Definition list.h:66
Homogeneous list of element cells of type T.
Definition list.h:106
static constexpr std::string_view kind_id
Wire kind id of this typed list ("l:" + T::kind_id).
Definition list.h:267
std::span< element_storage_t > as_span() noexcept
Mutable span view over the list's elements.
Definition list.h:153
std::partial_ordering compare(ICell const &other) const override
Element-wise three-way comparison; ignores display info.
Definition list.h:309
static cell_t from_json(json_t const &j, ParcelRegistry const &reg)
Deserialize a TypedListCell<T> from JSON.
Definition list.h:338
TypedListCell(std::from_range_t, R &&r)
Construct from any input range whose elements convert to element_storage_t.
Definition list.h:131
TypedListCell(std::span< const element_storage_t > sp)
Construct by copying the contents of sp into the list.
Definition list.h:137
std::string to_string() const override
Render the list as [a, b, c] using each element's to_string.
Definition list.h:273
static cell_type_descriptor_t descriptor()
Cached descriptor for this typed list.
Definition list.h:385
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition list.h:288
std::span< const element_storage_t > as_span() const noexcept
Read-only span view over the list's elements.
Definition list.h:146
TypedListCell(std::initializer_list< element_storage_t > elems)
Construct from a brace-enclosed list of element values.
Definition list.h:120
comms::DisplayInfo DisplayInfo
Display info attached to a cell or descriptor — re-exported from comms::DisplayInfo.
Definition common.h:75
constexpr std::string_view prefixed_id_v
Convenience alias yielding the joined std::string_view.
Definition common.h:175
auto cell(T &&v)
Wrap a raw value into its default cell, returning a shared_ptr to the cell.
Definition defaults.h:192
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
ParcelRegistry, Definition, and BuiltinsOptions for polymorphic dispatch.
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::vector< T::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