VLink  2.1.0
A high-performance communication middleware
serializer.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file serializer.h
26  * @brief Compile-time codec dispatch for VLink message payloads.
27  *
28  * @details
29  * The @c Serializer namespace is the codec router used by every VLink
30  * primitive. Given a C++ type @c T it determines, at compile time, which
31  * encoding family applies (raw bytes, Protobuf, FlatBuffers, FastDDS CDR,
32  * standard-layout POD, etc.) and dispatches @c serialize() / @c deserialize()
33  * to the appropriate code path with zero runtime cost.
34  *
35  * Application code rarely calls these helpers directly; @c Publisher,
36  * @c Subscriber, @c Client, @c Server, @c Setter, and @c Getter call them
37  * internally as part of their @c publish() / @c listen() / @c invoke() /
38  * @c set() / @c get() implementations.
39  *
40  * @par Codec Table -- @c Serializer::Type Enum
41  * | Constant | C++ criterion | Trait | Notes |
42  * | -------------------- | -------------------------------- | ----------------------- | ------------------ |
43  * | @c kBytesType | @c T is @c Bytes | @c is_bytes_type | Pass-through. |
44  * | @c kDynamicType | Has @c is_vlink_dynamic_data() | @c is_dynamic_type | Dynamic data. |
45  * | @c kCdrType | FastDDS IDL or ROS 2 type | @c is_cdr_type | CDR bytes. |
46  * | @c kProtoType | Protobuf-like value | @c is_proto_type | Protobuf value. |
47  * | @c kProtoPtrType | Protobuf-like pointer | @c is_proto_ptr_type | Caller-owned. |
48  * | @c kFlatTableType | FlatBuffers NativeTable | @c is_flat_table_type | Object API. |
49  * | @c kFlatPtrType | Pointer to @c flatbuffers::Table | @c is_flat_ptr_type | Zero-copy view. |
50  * | @c kFlatBuilderType | Has @c fbb_ and @c Finish() | @c is_flat_builder_type | Builder. |
51  * | @c kCustomType | Has @c operator>>/<<(Bytes&) | @c is_custom_type | Custom codec. |
52  * | @c kStringType | @c T is @c std::string | @c is_string_type | Byte string. |
53  * | @c kCharsType | Character pointer or array | @c is_chars_type | Serialise only. |
54  * | @c kStreamType | Supports @c std::stringstream | @c is_stream_type | Fallback. |
55  * | @c kStandardType | Trivial standard-layout value | @c is_standard_type | Byte copy. |
56  * | @c kStandardPtrType | Pointer to trivial standard type | @c is_standard_ptr_type | Zero-copy pointer. |
57  *
58  * Most value-like detectors unwrap @c std::shared_ptr<T> before matching
59  * (e.g. Protobuf values, CDR values, FlatBuffers native tables, custom
60  * codecs, strings, stream types, and standard-layout values).
61  * @par Detection Precedence Flow
62  * @verbatim
63  * get_type_of<T>() probes traits in this fixed order; first match wins:
64  *
65  * Bytes --(no)--> Dynamic --(no)--> CDR --(no)--> Proto
66  * |
67  * v (no)
68  * FlatPtr <--(no)-- FlatTable <--(no)-- ProtoPtr <--(no)--+
69  * |
70  * v (no)
71  * FlatBuilder --(no)--> Custom --(no)--> String --(no)--> Chars
72  * |
73  * v (no)
74  * Stream <--(no)-- StandardPtr <--(no)-- Standard <--(no)----+
75  * |
76  * v (no)
77  * kUnknownType
78  * @endverbatim
79  *
80  * @par Type Detection Example
81  * @code
82  * constexpr auto t = vlink::Serializer::get_type_of<MyProto>(); // -> kProtoType
83  * static_assert(vlink::Serializer::is_supported(t));
84  *
85  * constexpr auto u = vlink::Serializer::get_type_of<int>(); // -> kStandardType (POD)
86  * constexpr auto v = vlink::Serializer::get_type_of<std::string>(); // -> kStringType
87  * constexpr auto w = vlink::Serializer::get_type_of<const char*>(); // -> kCharsType
88  * @endcode
89  *
90  * @par Serialise and Deserialise
91  * @code
92  * MyProto msg;
93  * vlink::Bytes bytes;
94  * vlink::Serializer::serialize(msg, bytes);
95  *
96  * MyProto out;
97  * vlink::Serializer::deserialize(bytes, out);
98  * @endcode
99  *
100  * @par Custom Codec
101  * @code
102  * struct MyCustomMsg {
103  * int x;
104  * void operator>>(vlink::Bytes& out) const { ... } // serialise
105  * void operator<<(const vlink::Bytes& in) { ... } // deserialise
106  * };
107  * // vlink::Serializer::get_type_of<MyCustomMsg>() == vlink::Serializer::kCustomType
108  * @endcode
109  *
110  * @par Explicit Codec Selection
111  * CDR serialization produces a byte stream containing the 4-byte DDS
112  * encapsulation header. Use the explicit overload when the codec cannot be
113  * inferred from @c T:
114  * @code
115  * vlink::Serializer::serialize<vlink::Serializer::kCdrType>(msg, bytes, vlink::TransportType::kDds);
116  * @endcode
117  *
118  * @note Most entry points are header-defined templates; a few non-template
119  * overloads are declared @c static or @c inline where appropriate.
120  *
121  * @see base/bytes.h, impl/types.h
122  */
123 
124 #pragma once
125 
126 #include <string>
127 
128 #include "./base/bytes.h"
129 #include "./impl/types.h"
130 
131 namespace vlink {
132 
133 /**
134  * @namespace Serializer
135  * @brief Compile-time codec detection and dispatch for VLink message payloads.
136  *
137  * @details
138  * Header-defined helper namespace. Most entry points are templates so the
139  * full codec chain is resolved at compile time. Application code rarely
140  * uses this namespace directly; the framework invokes it internally inside
141  * @c publish() / @c listen() / @c invoke() / @c set() / @c get().
142  */
143 namespace Serializer { // NOLINT(readability-identifier-naming)
144 
145 /**
146  * @enum Type
147  * @brief Identifies the codec to use for a given C++ message type.
148  *
149  * @details
150  * Resolved at compile time by @c get_type_of<T>() and stored as a
151  * @c constexpr member on every primitive class, so all codec dispatch is
152  * zero-cost at runtime.
153  */
154 enum Type : uint8_t {
155  kUnknownType = 0, ///< Unsupported type; @c is_supported() returns @c false.
156  kBytesType = 1, ///< @c Bytes -- raw byte pass-through.
157  kDynamicType = 2, ///< VLink dynamic typed data.
158  kCustomType = 3, ///< User-defined codec via @c operator>>/<<.
159  kCdrType = 4, ///< FastDDS CDR via @c serialize(Cdr&) / @c deserialize(Cdr&).
160  kProtoType = 5, ///< Protobuf-like value.
161  kProtoPtrType = 6, ///< Protobuf-like raw pointer; caller-owned.
162  kFlatTableType = 7, ///< FlatBuffers NativeTable (object API).
163  kFlatPtrType = 8, ///< Pointer to @c flatbuffers::Table (zero-copy view).
164  kFlatBuilderType = 9, ///< FlatBuffers builder (@c fbb_ + @c Finish()).
165  kStringType = 10, ///< @c std::string payload bytes; no text-encoding validation.
166  kCharsType = 11, ///< C string serialisation; deserialise as @c std::string.
167  kStreamType = 12, ///< Stream-serialisable via @c std::stringstream.
168  kStandardType = 13, ///< Trivial standard-layout struct (POD value).
169  kStandardPtrType = 14, ///< Pointer to trivial standard-layout struct (POD pointer).
170 };
171 
172 /**
173  * @brief Reports whether @p type identifies a usable codec.
174  *
175  * @details
176  * @c kUnknownType is the only unsupported value. Support may be directional:
177  * @c kCharsType supports C-string serialisation but requires @c std::string on
178  * the deserialisation side. This function is invoked from the @c static_assert
179  * in every primitive constructor so unknown message types fail at compile time
180  * with a clear diagnostic.
181  *
182  * @param type Codec enumerator.
183  * @return @c false only for @c kUnknownType.
184  */
185 [[maybe_unused]] [[nodiscard]] static constexpr bool is_supported(Type type) noexcept;
186 
187 /**
188  * @brief Resolves the codec @c Type for @c T at compile time.
189  *
190  * @details
191  * Evaluates the @c if-constexpr chain documented above and returns the
192  * first matching enumerator. Returns @c kUnknownType if no codec matches.
193  *
194  * @tparam T C++ message type to classify.
195  * @return Resolved @c Type enumerator.
196  */
197 template <typename T>
198 [[nodiscard]] static constexpr Type get_type_of() noexcept;
199 
200 /**
201  * @brief Returns the coarse schema family for @c T with an explicit codec tag.
202  *
203  * @tparam TypeT Explicit VLink codec kind.
204  * @tparam T C++ message type to classify.
205  * @return @c SchemaType::kProtobuf, @c kFlatbuffers, @c kZeroCopy, @c kCdr, or @c kRaw.
206  */
207 template <Type TypeT, typename T>
208 [[nodiscard]] static constexpr SchemaType get_schema_type() noexcept;
209 
210 /**
211  * @brief Returns the coarse schema family inferred from @c T alone.
212  *
213  * @tparam T C++ message type to classify.
214  * @return @c SchemaType::kProtobuf, @c kFlatbuffers, @c kZeroCopy, @c kCdr, or @c kRaw.
215  */
216 template <typename T>
217 [[nodiscard]] static constexpr SchemaType get_schema_type() noexcept;
218 
219 /**
220  * @brief Returns the serialised type-name string for @c T with explicit codec tag.
221  *
222  * @details
223  * Used by the framework for cross-peer type matching (DDS topic type name,
224  * Protobuf fully-qualified name, FlatBuffers table name, etc.). Returns an
225  * empty string for codecs with no meaningful type name (e.g. @c kBytesType).
226  *
227  * @tparam TypeT Explicit codec kind.
228  * @tparam T C++ message type.
229  * @return Type-name string; empty if not applicable.
230  */
231 template <Type TypeT, typename T>
232 [[nodiscard]] static std::string get_serialized_type() noexcept;
233 
234 /**
235  * @brief Returns the serialised type-name string for @c T (codec auto-detected).
236  *
237  * @tparam T C++ message type.
238  * @return Type-name string; empty if not applicable.
239  */
240 template <typename T>
241 [[nodiscard]] static std::string get_serialized_type() noexcept;
242 
243 /**
244  * @brief Returns a serialised-size hint for @p src with explicit codec tag.
245  *
246  * @details
247  * Used to size loaned buffers ahead of serialisation. The returned value is
248  * an exact byte count only for codecs that can produce one cheaply; it is
249  * @c 0 for codecs that cannot report an upfront size (e.g. @c kBytesType,
250  * @c kStringType, @c kFlatTableType, @c kStandardType).
251  *
252  * @tparam TypeT Codec kind.
253  * @tparam T C++ message type.
254  * @param src Source value to measure.
255  * @return Byte-count hint; @c 0 if unknown.
256  */
257 template <Type TypeT, typename T>
258 [[nodiscard]] static size_t get_serialized_size(const T& src) noexcept;
259 
260 /**
261  * @brief Returns a serialised-size hint for @p src (codec auto-detected).
262  *
263  * @tparam T C++ message type.
264  * @param src Source value to measure.
265  * @return Byte-count hint; @c 0 if unknown.
266  */
267 template <typename T>
268 [[nodiscard]] static size_t get_serialized_size(const T& src) noexcept;
269 
270 /**
271  * @brief Serialises @p src into @p des with explicit codec and transport tags.
272  *
273  * @details
274  * @p transport identifies the active transport. CDR output is the same
275  * encapsulated byte representation for every transport. @p offset prepends
276  * that many zero bytes before the payload (used internally by some transports
277  * for framing).
278  *
279  * For @c kFlatBuilderType, serialisation calls the builder's @c Finish()
280  * path so @p src may be mutated. Because the final size is unavailable
281  * before @c Finish(), its size hint is @c 0 and a loaned destination is
282  * rejected without changing either the loan or the builder. Successful
283  * serialisation returns an owning copy.
284  *
285  * @tparam TypeT Codec kind.
286  * @tparam T C++ message type.
287  * @param src Source value to serialise.
288  * @param des Destination @c Bytes buffer (may be loaned).
289  * @param transport Active transport back-end.
290  * @param offset Number of header bytes to prepend (default @c 0).
291  * @return @c true on success; @c false on codec failure.
292  */
293 template <Type TypeT, typename T>
294 static bool serialize(const T& src, Bytes& des, TransportType transport = TransportType::kUnknown, uint8_t offset = 0);
295 
296 /**
297  * @brief Serialises @p src into @p des (codec and transport auto-detected).
298  *
299  * @tparam T C++ message type.
300  * @param src Source value.
301  * @param des Destination @c Bytes buffer.
302  * @return @c true on success.
303  */
304 template <typename T>
305 static bool serialize(const T& src, Bytes& des);
306 
307 /**
308  * @brief Serialises into transport-provided storage when available.
309  *
310  * @details
311  * When @p use_loan is true and a non-zero size hint is available, @p loan is
312  * called and may return either loaned or owning storage. A zero hint falls
313  * back to normal owning serialisation. FlatBuilder sources are finished
314  * before requesting their exact-size destination, including when that request
315  * subsequently fails. Other codecs use @c get_serialized_size() followed by
316  * the normal @c serialize() path. A non-zero size hint requires storage of
317  * exactly that size. A codec must not replace transport-loaned storage.
318  *
319  * @tparam TypeT Codec kind.
320  * @tparam T C++ message type.
321  * @tparam LoanCallbackT Callable compatible with @c Bytes(size_t).
322  * @param src Source value to serialise.
323  * @param des Destination populated on success; it may be modified on failure.
324  * @param transport Active transport back-end.
325  * @param use_loan Whether to request transport-provided storage.
326  * @param loan Destination provider called at most once.
327  * @return @c true on success; @c false on allocation, size, or codec failure.
328  */
329 template <Type TypeT, typename T, typename LoanCallbackT>
330 static bool serialize_to_transport(const T& src, Bytes& des, TransportType transport, bool use_loan,
331  LoanCallbackT&& loan);
332 
333 /**
334  * @brief Deserialises @p src into @p des with explicit codec and transport tags.
335  *
336  * @details
337  * CDR input must contain its DDS encapsulation header and is decoded
338  * identically for every transport. @c kCharsType destinations are rejected
339  * because a raw pointer cannot carry ownership of the required null-terminated
340  * storage. Use @c std::string for deserialisation.
341  *
342  * @tparam TypeT Codec kind.
343  * @tparam T C++ message type.
344  * @param src Source @c Bytes buffer.
345  * @param des Destination value to fill.
346  * @param transport Active transport back-end.
347  * @return @c true on success; @c false on parse failure.
348  */
349 template <Type TypeT, typename T>
350 static bool deserialize(const Bytes& src, T& des, TransportType transport = TransportType::kUnknown);
351 
352 /**
353  * @brief Deserialises @p src into @p des (codec and transport auto-detected).
354  *
355  * @tparam T C++ message type.
356  * @param src Source @c Bytes buffer.
357  * @param des Destination value.
358  * @return @c true on success.
359  */
360 template <typename T>
361 static bool deserialize(const Bytes& src, T& des);
362 
363 /**
364  * @brief Converts between two types where at least one side is @c Bytes.
365  *
366  * @details
367  * A compile-time @c static_assert enforces that @c SrcT or @c DesT (or both)
368  * is @c Bytes. The three cases are:
369  * - Both @c Bytes: shallow-copies @p src to @p des.
370  * - @c DesT == @c Bytes: dispatches to @c serialize().
371  * - @c SrcT == @c Bytes: dispatches to @c deserialize().
372  *
373  * @tparam SrcT Source type.
374  * @tparam DesT Destination type.
375  * @param src Source value.
376  * @param des Destination value.
377  * @return @c true on success.
378  */
379 template <typename SrcT, typename DesT>
380 static bool convert(const SrcT& src, DesT& des);
381 
382 /**
383  * @brief Dereferences a value, unwrapping @c std::shared_ptr when present.
384  *
385  * @details
386  * If @c T is @c std::shared_ptr<U>, returns @c *t; otherwise returns @c t.
387  * Internal helper so codec code can treat both value and shared-pointer
388  * inputs uniformly.
389  *
390  * @tparam T Input type (value or @c shared_ptr).
391  * @param t Input value.
392  * @return Reference to the underlying value.
393  */
394 template <typename T>
395 [[nodiscard]] static constexpr auto& deref(const T& t) noexcept;
396 
397 /**
398  * @brief Reports whether @c T is exactly @c Bytes.
399  *
400  * @tparam T Type to test.
401  * @return @c true for @c Bytes.
402  */
403 template <typename T>
404 [[nodiscard]] static constexpr bool is_bytes_type() noexcept;
405 
406 /**
407  * @brief Reports whether @c T is a VLink dynamic data type.
408  *
409  * @details
410  * Dynamic types expose an @c is_vlink_dynamic_data() member.
411  *
412  * @tparam T Type to test.
413  * @return @c true for dynamic data types.
414  */
415 template <typename T>
416 [[nodiscard]] static constexpr bool is_dynamic_type() noexcept;
417 
418 /**
419  * @brief Reports whether @c T is a FastDDS CDR-serialisable type.
420  *
421  * @details
422  * Requires @c VLINK_HAS_CDR, plus either both
423  * @c serialize(Cdr&) and @c deserialize(Cdr&) methods, or a type name
424  * carrying the @c VLINK_DDS_IDL_PREFIX prefix. When @c VLINK_HAS_ROS2 is
425  * defined, ROS2 message traits are also recognised.
426  *
427  * @tparam T Type to test.
428  * @return @c true for supported CDR types.
429  */
430 template <typename T>
431 [[nodiscard]] static constexpr bool is_cdr_type() noexcept;
432 
433 /**
434  * @brief Reports whether @c T is a Protobuf-like message value type.
435  *
436  * @details
437  * Requires Protobuf to be available and the type to expose
438  * @c SerializeToArray() and @c ParseFromArray() methods.
439  *
440  * @tparam T Type to test.
441  * @return @c true for Protobuf-compatible value types.
442  */
443 template <typename T>
444 [[nodiscard]] static constexpr bool is_proto_type() noexcept;
445 
446 /**
447  * @brief Reports whether @c T is a raw pointer to a Protobuf-like message.
448  *
449  * @details
450  * The pointee is not owned by the serialiser and must be non-null whenever
451  * the codec path dereferences it.
452  *
453  * @tparam T Pointer type to test.
454  * @return @c true for Protobuf-compatible pointer types.
455  */
456 template <typename T>
457 [[nodiscard]] static constexpr bool is_proto_ptr_type() noexcept;
458 
459 /**
460  * @brief Reports whether @c T is a FlatBuffers NativeTable type.
461  *
462  * @details
463  * Requires @c flatbuffers and the type (or its @c shared_ptr element type)
464  * to derive from @c flatbuffers::NativeTable.
465  *
466  * @tparam T Type to test.
467  * @return @c true for FlatBuffers NativeTable types.
468  */
469 template <typename T>
470 [[nodiscard]] static constexpr bool is_flat_table_type() noexcept;
471 
472 /**
473  * @brief Reports whether @c T is a FlatBuffers builder type.
474  *
475  * @details
476  * Requires @c flatbuffers and the type to expose both an @c fbb_ member
477  * and a @c Finish() method.
478  *
479  * @tparam T Type to test.
480  * @return @c true for FlatBuffers builder types.
481  */
482 template <typename T>
483 [[nodiscard]] static constexpr bool is_flat_builder_type() noexcept;
484 
485 /**
486  * @brief Reports whether @c T is a raw pointer to a @c flatbuffers::Table.
487  *
488  * @tparam T Pointer type to test.
489  * @return @c true for FlatBuffers Table pointer types.
490  */
491 template <typename T>
492 [[nodiscard]] static constexpr bool is_flat_ptr_type() noexcept;
493 
494 /**
495  * @brief Reports whether @c T provides a custom @c operator>>/<< codec.
496  *
497  * @details
498  * Checked via @c Traits::Operatorable for @c operator>>(Bytes&) and
499  * @c operator<<(const Bytes&).
500  *
501  * @tparam T Type to test.
502  * @return @c true for custom-codec types.
503  */
504 template <typename T>
505 [[nodiscard]] static constexpr bool is_custom_type() noexcept;
506 
507 /**
508  * @brief Reports whether @c T is @c std::string after unwrapping @c shared_ptr.
509  *
510  * @tparam T Type to test.
511  * @return @c true for @c std::string and @c std::shared_ptr<std::string>.
512  */
513 template <typename T>
514 [[nodiscard]] static constexpr bool is_string_type() noexcept;
515 
516 /**
517  * @brief Reports whether @c T is a pointer or array of non-volatile @c char.
518  *
519  * @details
520  * Matches @c char*, @c const char*, and string literal source types for
521  * serialisation. Deserialisation into a raw character pointer is rejected
522  * because the pointer cannot own the decoded storage; use @c std::string as
523  * the destination. The trait deliberately excludes other string-like types
524  * such as @c std::string_view because the chars codec requires a null-terminated
525  * source when serialising. Arrays must contain a null terminator within their
526  * extent; pointer callers must guarantee that a terminator is reachable. Bytes
527  * after the first terminator are ignored.
528  *
529  * @tparam T Type to test.
530  * @return @c true for C-string-compatible types.
531  */
532 template <typename T>
533 [[nodiscard]] static constexpr bool is_chars_type() noexcept;
534 
535 /**
536  * @brief Reports whether @c T supports bidirectional @c std::stringstream streaming.
537  *
538  * @details
539  * Detected via @c Traits::Operatorable<std::stringstream, T>(); the check
540  * requires both @c ss << t and @c ss >> t to be well-formed. Higher-priority
541  * codecs are checked first in @c get_type_of(), so this function is only
542  * reached for types that fail every earlier trait.
543  *
544  * @tparam T Type to test.
545  * @return @c true for stream-serialisable types.
546  */
547 template <typename T>
548 [[nodiscard]] static constexpr bool is_stream_type() noexcept;
549 
550 /**
551  * @brief Reports whether @c T is a trivial standard-layout value (POD).
552  *
553  * @details
554  * Matches non-pointer types where both @c std::is_trivial_v and
555  * @c std::is_standard_layout_v hold. Such types are byte-copied into and
556  * out of a @c Bytes buffer of @c sizeof(T) bytes.
557  *
558  * @tparam T Type to test.
559  * @return @c true for POD value types.
560  */
561 template <typename T>
562 [[nodiscard]] static constexpr bool is_standard_type() noexcept;
563 
564 /**
565  * @brief Reports whether @c T is a pointer to a trivial standard-layout type.
566  *
567  * @details
568  * Matches @c U* where @c std::is_trivial_v<U> && @c std::is_standard_layout_v<U>.
569  * The pointer is reinterpreted (not copied through) for zero-copy use.
570  *
571  * @tparam T Pointer type to test.
572  * @return @c true for POD-pointer types.
573  */
574 template <typename T>
575 [[nodiscard]] static constexpr bool is_standard_ptr_type() noexcept;
576 
577 } // namespace Serializer
578 
579 } // namespace vlink
580 
581 #include "./internal/serializer-inl.h"
Canonical 128-byte binary payload carrier with inline storage, multi-mode ownership and LZAV compress...
Compile-time codec detection and dispatch for VLink message payloads.
Core enumerations and small value types shared by the entire VLink implementation layer.