This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit f3a56cca677e4c4b54cf24e587ab41bcaac811db Author: chaokunyang <[email protected]> AuthorDate: Wed Apr 29 13:33:15 2026 +0000 π synced local 'docs/guide/' with remote 'docs/guide/' --- docs/guide/cpp/field-configuration.md | 576 +++++---------------------- docs/guide/cpp/polymorphism.md | 46 +-- docs/guide/xlang/field-nullability.md | 27 +- docs/guide/xlang/field-reference-tracking.md | 12 +- docs/guide/xlang/field-type-meta.md | 28 +- 5 files changed, 163 insertions(+), 526 deletions(-) diff --git a/docs/guide/cpp/field-configuration.md b/docs/guide/cpp/field-configuration.md index 94f5170dd..02029453a 100644 --- a/docs/guide/cpp/field-configuration.md +++ b/docs/guide/cpp/field-configuration.md @@ -19,550 +19,188 @@ license: | limitations under the License. --- -This page explains how to configure field-level metadata for serialization. - -## Overview - -Apache Foryβ’ provides three ways to configure field-level metadata at compile time: - -1. **`fory::field<>` template** - Inline metadata in struct definition with wrapper types -2. **`FORY_FIELD_TAGS` macro** - Non-invasive metadata for basic field configuration -3. **`FORY_FIELD_CONFIG` macro** - Advanced configuration with builder pattern and encoding control - -These enable: - -- **Tag IDs**: Assign compact numeric IDs for schema evolution -- **Nullability**: Mark pointer fields as nullable -- **Reference Tracking**: Enable reference tracking for shared pointers -- **Encoding Control**: Specify wire format for integers (varint, fixed, tagged) -- **Dynamic Dispatch**: Control polymorphic type info for smart pointers - -**Comparison:** - -| Feature | `fory::field<>` | `FORY_FIELD_TAGS` | `FORY_FIELD_CONFIG` | -| ----------------------- | --------------------- | ----------------- | ------------------------- | -| **Struct modification** | Required (wrap types) | None | None | -| **Encoding control** | No | No | Yes (varint/fixed/tagged) | -| **Builder pattern** | No | No | Yes | -| **Dynamic control** | Yes | No | Yes | -| **Compile-time verify** | Yes | Limited | Yes (member pointers) | -| **Cross-lang compat** | Limited | Limited | Full | -| **Recommended for** | Simple structs | Third-party types | Complex/xlang structs | - -## The fory::field Template - -```cpp -template <typename T, int16_t Id, typename... Options> -class field; -``` - -### Template Parameters - -| Parameter | Description | -| --------- | ------------------------------------------------ | -| `T` | The underlying field type | -| `Id` | Field tag ID (int16_t) for compact serialization | -| `Options` | Optional tags: `fory::nullable`, `fory::ref` | - -### Basic Usage +Field configuration is embedded directly in `FORY_STRUCT`. A field entry may be +bare, or it may be a tuple containing the member name and a `fory::F(...)` +builder: ```cpp #include "fory/serialization/fory.h" -using namespace fory::serialization; - -struct Person { - fory::field<std::string, 0> name; - fory::field<int32_t, 1> age; - fory::field<std::optional<std::string>, 2> nickname; +struct DataV2 { + uint32_t id; + uint64_t timestamp; + std::optional<uint32_t> version; }; -FORY_STRUCT(Person, name, age, nickname); -``` -The `fory::field<>` wrapper is transparent - you can use it like the underlying type: - -```cpp -Person person; -person.name = "Alice"; // Direct assignment -person.age = 30; -std::string n = person.name; // Implicit conversion -int a = person.age.get(); // Explicit get() +FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version); ``` -## Tag Types +The configuration is compile-time metadata. It does not allocate codec objects +or add virtual dispatch on the serialization path. -### fory::nullable +## Field Identity -Marks a smart pointer field as nullable (can be `nullptr`): +`fory::F()` uses name-mode field identity. Bare fields are also name-mode: ```cpp -struct Node { - fory::field<std::string, 0> name; - fory::field<std::shared_ptr<Node>, 1, fory::nullable> next; // Can be nullptr -}; -FORY_STRUCT(Node, name, next); +FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version); ``` -**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>`, `std::unique_ptr<T>` - -**Note:** For nullable primitives or strings, use `std::optional<T>` instead: +`fory::F(id)` uses id-mode field identity: ```cpp -// Correct: use std::optional for nullable primitives -fory::field<std::optional<int32_t>, 0> optional_value; - -// Wrong: nullable is not allowed for primitives -// fory::field<int32_t, 0, fory::nullable> value; // Compile error! +FORY_STRUCT(DataV2, (id, fory::F(0)), (timestamp, fory::F(1).tagged()), + (version, fory::F(2))); ``` -### fory::not_null - -Explicitly marks a pointer field as non-nullable. This is the default for smart pointers, but can be used for documentation: - -```cpp -fory::field<std::shared_ptr<Data>, 0, fory::not_null> data; // Must not be nullptr -``` +A struct must use exactly one identity mode. If any field uses `fory::F(id)`, +every field in that `FORY_STRUCT` must use `fory::F(id)`. Mixed name/id mode is a +compile-time error. -**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>`, `std::unique_ptr<T>` +## Scalar Encoding -### fory::ref - -Enables reference tracking for shared pointer fields. When multiple fields reference the same object, it will be serialized once and shared: - -```cpp -struct Graph { - fory::field<std::string, 0> name; - fory::field<std::shared_ptr<Graph>, 1, fory::ref> left; // Ref tracked - fory::field<std::shared_ptr<Graph>, 2, fory::ref> right; // Ref tracked -}; -FORY_STRUCT(Graph, name, left, right); -``` - -**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>` (requires shared ownership) - -### fory::dynamic\<V\> - -Controls whether type info is written for polymorphic smart pointer fields: - -- `fory::dynamic<true>`: Force type info to be written (enable runtime subtype support) -- `fory::dynamic<false>`: skip type info (use declared type directly, no dynamic dispatch) - -By default, Fory auto-detects polymorphism via `std::is_polymorphic<T>`. Use this tag to override: +Integer encoding is configured on the field or on a nested value-node spec: ```cpp -// Base class with virtual methods (detected as polymorphic by default) -struct Animal { - virtual ~Animal() = default; - virtual std::string speak() const = 0; +struct Counters { + uint32_t fixed_id; + uint64_t tagged_time; + int64_t signed_score; }; -struct Zoo { - // Auto: type info written because Animal has virtual methods - fory::field<std::shared_ptr<Animal>, 0, fory::nullable> animal; - - // Force non-dynamic: skip type info even though Animal has virtual methods - // Use when you know the runtime type will always be exactly as declared - fory::field<std::shared_ptr<Animal>, 1, fory::nullable, fory::dynamic<false>> fixed_animal; -}; -FORY_STRUCT(Zoo, animal, fixed_animal); +FORY_STRUCT(Counters, (fixed_id, fory::F().fixed()), + (tagged_time, fory::F().tagged()), + (signed_score, fory::F().varint())); ``` -**Valid for:** `std::shared_ptr<T>`, `std::unique_ptr<T>` - -### Combining Tags +Supported scalar encoding methods are: -Multiple tags can be combined for shared pointers and SharedWeak: +| Method | Meaning | +| ---------- | -------------------------------------------- | +| `fixed()` | Fixed-width integer encoding where valid | +| `varint()` | Variable-length integer encoding where valid | +| `tagged()` | Tagged integer encoding where valid | -```cpp -// Nullable + ref tracking -fory::field<std::shared_ptr<Node>, 0, fory::nullable, fory::ref> link; -``` +Invalid scalar/type combinations fail at compile time. -## Type Rules +## Nested Specs -| Type | Allowed Options | Nullability | -| ------------------------------------ | ------------------------------- | ---------------------------------- | -| Primitives, strings | None | Use `std::optional<T>` if nullable | -| `std::optional<T>` | None | Inherently nullable | -| `std::shared_ptr<T>` | `nullable`, `ref`, `dynamic<V>` | Non-null by default | -| `fory::serialization::SharedWeak<T>` | `nullable`, `ref`, `dynamic<V>` | Non-null by default | -| `std::unique_ptr<T>` | `nullable`, `dynamic<V>` | Non-null by default | - -## Complete Example +Use the `fory::T` namespace for value-node specs inside containers and wrapper +carriers. Untyped specs infer the actual C++ type at that node: ```cpp -#include "fory/serialization/fory.h" - -using namespace fory::serialization; - -// Define a struct with various field configurations -struct Document { - // Required fields (non-nullable) - fory::field<std::string, 0> title; - fory::field<int32_t, 1> version; - - // Optional primitive using std::optional - fory::field<std::optional<std::string>, 2> description; +namespace T = fory::T; - // Nullable pointer - fory::field<std::unique_ptr<std::string>, 3, fory::nullable> metadata; - - // Reference-tracked shared pointer - fory::field<std::shared_ptr<Document>, 4, fory::ref> parent; - - // Nullable + reference-tracked - fory::field<std::shared_ptr<Document>, 5, fory::nullable, fory::ref> related; +struct Foo { + std::vector<uint32_t> values; + std::map<uint32_t, std::vector<int64_t>> nested; }; -FORY_STRUCT(Document, title, version, description, metadata, parent, related); - -int main() { - auto fory = Fory::builder().xlang(true).build(); - fory.register_struct<Document>(100); - - Document doc; - doc.title = "My Document"; - doc.version = 1; - doc.description = "A sample document"; - doc.metadata = nullptr; // Allowed because nullable - doc.parent = std::make_shared<Document>(); - doc.parent.get()->title = "Parent Doc"; - doc.related = nullptr; // Allowed because nullable - - auto bytes = fory.serialize(doc).value(); - auto decoded = fory.deserialize<Document>(bytes).value(); -} -``` - -## Compile-Time Validation - -Invalid configurations are caught at compile time: - -```cpp -// Error: nullable and not_null are mutually exclusive -fory::field<std::shared_ptr<int>, 0, fory::nullable, fory::not_null> bad1; -// Error: nullable only valid for smart pointers -fory::field<int32_t, 0, fory::nullable> bad2; - -// Error: ref only valid for shared_ptr -fory::field<std::unique_ptr<int>, 0, fory::ref> bad3; - -// Error: options not allowed for std::optional (inherently nullable) -fory::field<std::optional<int>, 0, fory::nullable> bad4; -``` - -## Backwards Compatibility - -Existing structs without `fory::field<>` wrappers continue to work: - -```cpp -// Old style - still works -struct LegacyPerson { - std::string name; - int32_t age; -}; -FORY_STRUCT(LegacyPerson, name, age); - -// New style with field metadata -struct ModernPerson { - fory::field<std::string, 0> name; - fory::field<int32_t, 1> age; -}; -FORY_STRUCT(ModernPerson, name, age); +FORY_STRUCT(Foo, + (values, fory::F().list(T::fixed())), + (nested, fory::F().map(T::varint(), + T::list(T::tagged())))); ``` -## FORY_FIELD_TAGS Macro - -The `FORY_FIELD_TAGS` macro provides a non-invasive way to add field metadata without modifying struct definitions. This is useful for: - -- **Third-party types**: Add metadata to types you don't own -- **Clean structs**: Keep struct definitions as pure C++ -- **Isolated dependencies**: Confine Fory headers to serialization config files - -### Usage +Typed specs are optional validators and make the intended node type explicit: ```cpp -// user_types.h - NO fory headers needed! -struct Document { - std::string title; - int32_t version; - std::optional<std::string> description; - std::shared_ptr<User> author; - std::shared_ptr<User> reviewer; - std::shared_ptr<Document> parent; - std::unique_ptr<Data> data; -}; - -// serialization_config.cpp - fory config isolated here -#include "fory/serialization/fory.h" -#include "user_types.h" - -FORY_STRUCT(Document, title, version, description, author, reviewer, parent, data) - -FORY_FIELD_TAGS(Document, - (title, 0), // string: non-nullable - (version, 1), // int: non-nullable - (description, 2), // optional: inherently nullable - (author, 3), // shared_ptr: non-nullable (default) - (reviewer, 4, nullable), // shared_ptr: nullable - (parent, 5, ref), // shared_ptr: non-nullable, with ref tracking - (data, 6, nullable) // unique_ptr: nullable -) +FORY_STRUCT(Foo, (nested, fory::F().map(T::uint32().varint(), + T::list(T::int64().tagged())))); ``` -### FORY_FIELD_TAGS Options - -| Field Type | Valid Combinations | -| -------------------- | ---------------------------------------------------------------------------------------- | -| Primitives, strings | `(field, id)` only | -| `std::optional<T>` | `(field, id)` only | -| `std::shared_ptr<T>` | `(field, id)`, `(field, id, nullable)`, `(field, id, ref)`, `(field, id, nullable, ref)` | -| `std::unique_ptr<T>` | `(field, id)`, `(field, id, nullable)` | +Supported recursive composition methods are: -## FORY_FIELD_CONFIG Macro +| Method | Applies to | +| ------------------- | ------------------------------------- | +| `list(elem)` | `std::vector<T>` and list-like fields | +| `set(elem)` | `std::set<T>` and set-like fields | +| `map(key, value)` | `std::map<K, V>` and map-like fields | +| `map().key(spec)` | Override only the map key | +| `map().value(spec)` | Override only the map value | +| `inner(child)` | Transparent single-child carriers | -The `FORY_FIELD_CONFIG` macro is the most powerful and flexible way to configure field-level serialization. It provides: - -- **Builder pattern API**: Fluent, chainable configuration with `F(id).option1().option2()` -- **Encoding control**: Specify how unsigned integers are encoded (varint, fixed, tagged) -- **Compile-time verification**: Field names are verified against member pointers -- **Cross-language compatibility**: Configure encoding to match other languages (Java, Rust, etc.) - -### Basic Syntax +Partial map overrides are useful when only one side needs a non-default +encoding: ```cpp -FORY_FIELD_CONFIG(StructType, - (field1, fory::F(0)), // Simple: just ID - (field2, fory::F(1).nullable()), // With nullable - (field3, fory::F(2).varint()), // With encoding - (field4, fory::F(3).nullable().ref()), // Multiple options - (field5, 4) // Backward compatible: integer ID -); +FORY_STRUCT(Foo, + (nested, fory::F().map().key(T::varint())), + (other, fory::F().map().value(T::list(T::tagged())))); ``` -### The F() Builder - -The `fory::F(id)` factory creates a `FieldMeta` object that supports method chaining: +## Carrier Inner Specs -```cpp -fory::F(0) // Create with field ID 0 - .nullable() // Mark as nullable - .ref() // Enable reference tracking - .varint() // Use variable-length encoding - .fixed() // Use fixed-size encoding - .tagged() // Use tagged encoding - .dynamic(false) // skip type info (no dynamic dispatch) - .dynamic(true) // Force type info (enable dynamic dispatch) - .compress(false) // Disable compression -``` - -**Tip:** To use `F()` without the `fory::` prefix, add a using declaration: +Use `.inner(...)` for wrapper-like carriers. The carrier kind still comes from +the actual C++ type, and controls nullable/reference behavior: ```cpp -using fory::F; +struct WrapperFields { + std::optional<std::vector<uint32_t>> maybe_values; + std::shared_ptr<std::vector<int64_t>> shared_values; +}; -FORY_FIELD_CONFIG(MyStruct, - (field1, F(0).varint()), // No prefix needed - (field2, F(1).nullable()) -); +FORY_STRUCT(WrapperFields, + (maybe_values, fory::F().inner(T::list(T::varint()))), + (shared_values, + fory::F().nullable().ref().inner(T::list(T::tagged())))); ``` -### Encoding Options for Unsigned Integers +`.inner(...)` is the only public combinator for `std::optional<T>`, +`std::shared_ptr<T>`, `std::unique_ptr<T>`, and +`fory::serialization::SharedWeak<T>`. -For `uint32_t` and `uint64_t` fields, you can specify the wire encoding: +## Nullability, Reference Tracking, And Dynamic Fields -| Method | Type ID | Description | Use Case | -| ----------- | ------------- | ---------------------------------------------- | ------------------------------------- | -| `.varint()` | VAR_UINT32/64 | Variable-length encoding (1-5 or 1-10 bytes) | Values typically small | -| `.fixed()` | UINT32/64 | Fixed-size encoding (always 4 or 8 bytes) | Values uniformly distributed | -| `.tagged()` | TAGGED_UINT64 | Tagged hybrid encoding with size hint (uint64) | Mixed small and large values (uint64) | - -**Note:** `uint8_t` and `uint16_t` always use fixed encoding (UINT8, UINT16). - -### Complete Example +`std::optional<T>` is nullable by default. Smart pointers may be marked nullable +or reference-tracked in the field spec: ```cpp -#include "fory/serialization/fory.h" - -using namespace fory::serialization; - -// Define struct with unsigned integer fields -struct MetricsData { - // Counters - often small values, use varint for space efficiency - uint32_t request_count; - uint64_t bytes_sent; - - // IDs - uniformly distributed, use fixed for consistent performance - uint32_t user_id; - uint64_t session_id; - - // Timestamps - use tagged encoding for mixed value ranges - uint64_t created_at; - - // Nullable fields - std::optional<uint32_t> error_count; - std::optional<uint64_t> last_access_time; +struct Node { + std::string name; + std::shared_ptr<Node> next; }; -FORY_STRUCT(MetricsData, request_count, bytes_sent, user_id, session_id, - created_at, error_count, last_access_time); - -// Configure field encoding -FORY_FIELD_CONFIG(MetricsData, - // Small counters - varint saves space - (request_count, fory::F(0).varint()), - (bytes_sent, fory::F(1).varint()), - - // IDs - fixed for consistent performance - (user_id, fory::F(2).fixed()), - (session_id, fory::F(3).fixed()), - - // Timestamp - tagged encoding - (created_at, fory::F(4).tagged()), - - // Nullable fields - (error_count, fory::F(5).nullable().varint()), - (last_access_time, fory::F(6).nullable().tagged()) -); - -int main() { - auto fory = Fory::builder().xlang(true).build(); - fory.register_struct<MetricsData>(100); - - MetricsData data; - data.request_count = 42; - data.bytes_sent = 1024; - data.user_id = 12345678; - data.session_id = 9876543210; - data.created_at = 1704067200000000000ULL; // 2024-01-01 in nanoseconds - data.error_count = 3; - data.last_access_time = std::nullopt; - - auto bytes = fory.serialize(data).value(); - auto decoded = fory.deserialize<MetricsData>(bytes).value(); -} +FORY_STRUCT(Node, name, (next, fory::F().nullable().ref())); ``` -### Cross-Language Compatibility - -When serializing data to be read by other languages, use `FORY_FIELD_CONFIG` to match their encoding expectations: - -**Java Compatibility:** +For polymorphic pointer fields, use `.dynamic(true)` to always write runtime +type information, `.dynamic(false)` to use the declared type directly, or omit +it to let Fory infer the behavior from the C++ type: ```cpp -// Java uses these type IDs for unsigned integers: -// - Byte (u8): UINT8 (fixed) -// - Short (u16): UINT16 (fixed) -// - Integer (u32): VAR_UINT32 (varint) or UINT32 (fixed) -// - Long (u64): VAR_UINT64 (varint), UINT64 (fixed), or TAGGED_UINT64 - -struct JavaCompatible { - uint8_t byte_field; // Maps to Java Byte - uint16_t short_field; // Maps to Java Short - uint32_t int_var_field; // Maps to Java Integer with varint - uint32_t int_fixed_field; // Maps to Java Integer with fixed - uint64_t long_var_field; // Maps to Java Long with varint - uint64_t long_tagged; // Maps to Java Long with tagged +struct Zoo { + std::shared_ptr<Animal> star; + std::shared_ptr<Animal> mascot; }; -FORY_STRUCT(JavaCompatible, byte_field, short_field, int_var_field, - int_fixed_field, long_var_field, long_tagged); - -FORY_FIELD_CONFIG(JavaCompatible, - (byte_field, fory::F(0)), // UINT8 (auto) - (short_field, fory::F(1)), // UINT16 (auto) - (int_var_field, fory::F(2).varint()), // VAR_UINT32 - (int_fixed_field, fory::F(3).fixed()), // UINT32 - (long_var_field, fory::F(4).varint()), // VAR_UINT64 - (long_tagged, fory::F(5).tagged()) // TAGGED_UINT64 -); +FORY_STRUCT(Zoo, (star, fory::F().nullable().dynamic(true)), + (mascot, fory::F().nullable().dynamic(false))); ``` -### Schema Evolution with FORY_FIELD_CONFIG +## Unions -In compatible mode, fields can have different nullability between sender and receiver: +`FORY_UNION` cases must use explicit ids. Name-mode `fory::F()` is invalid for +union metadata: ```cpp -// Version 1: All fields non-nullable -struct DataV1 { - uint32_t id; - uint64_t timestamp; -}; -FORY_STRUCT(DataV1, id, timestamp); -FORY_FIELD_CONFIG(DataV1, - (id, fory::F(0).varint()), - (timestamp, fory::F(1).tagged()) -); +struct Choice { + std::variant<std::string, uint32_t> value; -// Version 2: Added nullable fields -struct DataV2 { - uint32_t id; - uint64_t timestamp; - std::optional<uint32_t> version; // New nullable field + static Choice text(std::string value); + static Choice code(uint32_t value); }; -FORY_STRUCT(DataV2, id, timestamp, version); -FORY_FIELD_CONFIG(DataV2, - (id, fory::F(0).varint()), - (timestamp, fory::F(1).tagged()), - (version, fory::F(2).nullable().varint()) // New field with nullable -); -``` - -### FORY_FIELD_CONFIG Options Reference - -| Method | Description | Valid For | -| ----------------- | ------------------------------------------------ | ---------------------------------------------------- | -| `.nullable()` | Mark field as nullable | Smart pointers, primitives | -| `.ref()` | Enable reference tracking | `std::shared_ptr`, `fory::serialization::SharedWeak` | -| `.dynamic(true)` | Force type info to be written (dynamic dispatch) | Smart pointers | -| `.dynamic(false)` | skip type info (use declared type directly) | Smart pointers | -| `.varint()` | Use variable-length encoding | `uint32_t`, `uint64_t` | -| `.fixed()` | Use fixed-size encoding | `uint32_t`, `uint64_t` | -| `.tagged()` | Use tagged hybrid encoding | `uint64_t` only | -| `.compress(v)` | Enable/disable field compression | All types | - -## Default Values - -- **Nullable**: Only `std::optional<T>` is nullable by default; all other types (including `std::shared_ptr`) are non-nullable -- **Ref tracking**: Enabled by default for `std::shared_ptr<T>` and `fory::serialization::SharedWeak<T>`, disabled for other types unless configured -You **need to configure fields** when: +FORY_UNION(Choice, (text, std::string, fory::F(1)), + (code, uint32_t, fory::F(2).fixed())); +``` -- A field can be null (use `std::optional<T>` or mark with `nullable()`) -- A field needs reference tracking for shared/circular objects (use `ref()`) -- Integer types need specific encoding for cross-language compatibility -- You want to reduce metadata size (use field IDs) +Generated C++ may omit the explicit case type when it can infer the payload type +from a non-overloaded one-argument factory: ```cpp -// Xlang mode: explicit configuration required -struct User { - std::string name; // Non-nullable by default - std::optional<std::string> email; // Nullable (std::optional) - std::shared_ptr<User> friend_ptr; // Ref tracking by default -}; - -FORY_STRUCT(User, name, email, friend_ptr); - -FORY_FIELD_CONFIG(User, - (name, fory::F(0)), - (email, fory::F(1)), // nullable implicit for optional - (friend_ptr, fory::F(2).nullable().ref()) // explicit nullable + ref -); +FORY_UNION(GeneratedChoice, (text, fory::F(1)), + (code, fory::F(2).fixed())); ``` -### Default Values Summary - -| Type | Default Nullable | Default Ref Tracking | -| ------------------------------------ | ---------------- | -------------------- | -| Primitives, `string` | `false` | `false` | -| `std::optional<T>` | `true` | `false` | -| `std::shared_ptr<T>` | `false` | `true` | -| `fory::serialization::SharedWeak<T>` | `false` | `true` | -| `std::unique_ptr<T>` | `false` | `false` | - -## Related Topics - -- [Type Registration](type-registration.md) - Registering types with FORY_STRUCT -- [Schema Evolution](schema-evolution.md) - Using tag IDs for schema evolution -- [Configuration](configuration.md) - Enabling reference tracking globally -- [Cross-Language](cross-language.md) - Interoperability with Java, Rust, Python +The three-element form is the stable public form for handwritten code. diff --git a/docs/guide/cpp/polymorphism.md b/docs/guide/cpp/polymorphism.md index 97f5bad11..4465553ef 100644 --- a/docs/guide/cpp/polymorphism.md +++ b/docs/guide/cpp/polymorphism.md @@ -137,7 +137,8 @@ struct Container2 { ## Controlling Dynamic Dispatch -Use `fory::dynamic<V>` to override automatic polymorphism detection: +Use `fory::F().dynamic(V)` in `FORY_STRUCT` to override automatic +polymorphism detection: ```cpp struct Animal { @@ -150,23 +151,24 @@ struct Pet { std::shared_ptr<Animal> animal1; // Force dynamic: type info written explicitly - fory::field<std::shared_ptr<Animal>, 0, fory::dynamic<true>> animal2; + std::shared_ptr<Animal> animal2; // Force non-dynamic: skip type info (faster but no runtime subtyping) - fory::field<std::shared_ptr<Animal>, 1, fory::dynamic<false>> animal3; + std::shared_ptr<Animal> animal3; }; -FORY_STRUCT(Pet, animal1, animal2, animal3); +FORY_STRUCT(Pet, animal1, (animal2, fory::F().dynamic(true)), + (animal3, fory::F().dynamic(false))); ``` -**When to use `fory::dynamic<false>`:** +**When to use `dynamic(false)`:** - You know the runtime type will always match the declared type - Performance is critical and you don't need subtype support - Working with monomorphic data despite having a polymorphic base class -### Field Configuration Without Wrapper Types +### Field Configuration -Use `FORY_FIELD_CONFIG` to configure fields without `fory::field<>` wrapper: +Configure field metadata directly in `FORY_STRUCT`: ```cpp struct Zoo { @@ -174,17 +176,13 @@ struct Zoo { std::shared_ptr<Animal> backup; // Nullable polymorphic field std::shared_ptr<Animal> mascot; // Non-dynamic (no subtype dispatch) }; -FORY_STRUCT(Zoo, star, backup, mascot); - -// Configure fields with tag IDs and options -FORY_FIELD_CONFIG(Zoo, - (star, fory::F(0)), // Tag ID 0, default options - (backup, fory::F(1).nullable()), // Tag ID 1, allow nullptr - (mascot, fory::F(2).dynamic(false)) // Tag ID 2, disable polymorphism -); +FORY_STRUCT(Zoo, (star, fory::F(0)), + (backup, fory::F(1).nullable()), + (mascot, fory::F(2).dynamic(false))); ``` -See [Field Configuration](field-configuration.md) for complete details on `fory::nullable`, `fory::ref`, and other field-level options +See [Field Configuration](field-configuration.md) for complete details on +`nullable()`, `ref()`, and other field-level options. ## std::unique_ptr Polymorphism @@ -307,8 +305,8 @@ assert(!result.ok()); // Fails with depth exceeded error ## Nullability for Polymorphic Fields By default, `std::shared_ptr<T>` and `std::unique_ptr<T>` fields are treated as -non-nullable in the schema. To allow `nullptr`, wrap the field with -`fory::field<>` (or `FORY_FIELD_TAGS`) and opt in with `fory::nullable`. +non-nullable in the schema. To allow `nullptr`, mark the field nullable in +`FORY_STRUCT`. ```cpp struct Pet { @@ -316,9 +314,9 @@ struct Pet { std::shared_ptr<Animal> primary; // Nullable via explicit field metadata - fory::field<std::shared_ptr<Animal>, 0, fory::nullable> optional; + std::shared_ptr<Animal> optional; }; -FORY_STRUCT(Pet, primary, optional); +FORY_STRUCT(Pet, primary, (optional, fory::F().nullable())); ``` See [Field Configuration](field-configuration.md) for more details. @@ -415,10 +413,10 @@ auto fory = Fory::builder() auto fory = Fory::builder().max_dyn_depth(10).build(); ``` -7. **Use `fory::nullable`** for optional polymorphic fields: +7. **Use `nullable()`** for optional polymorphic fields: ```cpp - fory::field<std::shared_ptr<Base>, 0, fory::nullable> optional_ptr; + FORY_STRUCT(Holder, (optional_ptr, fory::F().nullable())); ``` ## Error Handling @@ -455,10 +453,10 @@ if (!decoded_result.ok()) { **Optimization tips:** -1. **Use `fory::dynamic<false>`** when runtime type matches declared type: +1. **Use `dynamic(false)`** when runtime type matches declared type: ```cpp - fory::field<std::shared_ptr<Base>, 0, fory::dynamic<false>> fixed_type; + FORY_STRUCT(Holder, (fixed_type, fory::F().dynamic(false))); ``` 2. **Minimize nesting depth** to reduce metadata overhead diff --git a/docs/guide/xlang/field-nullability.md b/docs/guide/xlang/field-nullability.md index ab888a563..de7f9dc2b 100644 --- a/docs/guide/xlang/field-nullability.md +++ b/docs/guide/xlang/field-nullability.md @@ -200,17 +200,32 @@ public class Config { } ``` -### C++: fory::field Wrapper +### C++: FORY_STRUCT Field Config ```cpp struct Config { - // Explicitly mark as nullable - fory::field<std::string, 1, fory::nullable<true>> optional_setting; + std::optional<std::string> optional_setting; + std::string required_setting; +}; + +FORY_STRUCT(Config, + (optional_setting, fory::F(1)), + (required_setting, fory::F(2)) +); +``` - // Explicitly mark as non-nullable (default) - fory::field<std::string, 2, fory::nullable<false>> required_setting; +For nullable pointer carriers, opt in with `.nullable()`: + +```cpp +struct ConfigRef { + std::shared_ptr<std::string> optional_setting; + std::shared_ptr<std::string> required_setting; }; -FORY_STRUCT(Config, optional_setting, required_setting); + +FORY_STRUCT(ConfigRef, + (optional_setting, fory::F(1).nullable()), + (required_setting, fory::F(2)) +); ``` ## Null Value Handling diff --git a/docs/guide/xlang/field-reference-tracking.md b/docs/guide/xlang/field-reference-tracking.md index 1ed94bd3d..2aafbf2ee 100644 --- a/docs/guide/xlang/field-reference-tracking.md +++ b/docs/guide/xlang/field-reference-tracking.md @@ -141,7 +141,7 @@ public class Document { } ``` -#### C++: fory::field Wrapper +#### C++: FORY_STRUCT Field Config ```cpp struct Document { @@ -151,10 +151,14 @@ struct Document { std::shared_ptr<Author> author; fory::serialization::SharedWeak<Data> data; - // Explicitly mark ref tracking when using field wrappers (optional) - fory::field<std::shared_ptr<Tag>, 1, fory::ref> tag_owner; + std::shared_ptr<Tag> tag_owner; }; -FORY_STRUCT(Document, title, author, data, tag_owner); +FORY_STRUCT(Document, + title, + author, + data, + (tag_owner, fory::F().ref()) +); ``` To disable reference tracking for C++ entirely, set diff --git a/docs/guide/xlang/field-type-meta.md b/docs/guide/xlang/field-type-meta.md index 1c6880c85..58870fd36 100644 --- a/docs/guide/xlang/field-type-meta.md +++ b/docs/guide/xlang/field-type-meta.md @@ -85,9 +85,7 @@ public class Container { ### C++ -C++ uses the `fory::dynamic<V>` template tag or `.dynamic(bool)` builder method: - -**Using `fory::field<>` template**: +C++ uses the `.dynamic(bool)` builder method inside `FORY_STRUCT`: ```cpp #include "fory/serialization/fory.h" @@ -100,29 +98,15 @@ struct Animal { struct Zoo { // Auto: type info written because Animal is polymorphic (std::is_polymorphic) - fory::field<std::shared_ptr<Animal>, 0, fory::nullable> animal; + std::shared_ptr<Animal> animal; // Force non-dynamic: skip type info even though Animal is polymorphic - fory::field<std::shared_ptr<Animal>, 1, fory::nullable, fory::dynamic<false>> fixed_animal; + std::shared_ptr<Animal> fixed_animal; // Force dynamic: write type info even for non-polymorphic types - fory::field<std::shared_ptr<Data>, 2, fory::dynamic<true>> polymorphic_data; -}; -FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data); -``` - -**Using `FORY_FIELD_CONFIG` macro**: - -```cpp -struct Zoo { - std::shared_ptr<Animal> animal; - std::shared_ptr<Animal> fixed_animal; std::shared_ptr<Data> polymorphic_data; }; - -FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data); - -FORY_FIELD_CONFIG(Zoo, +FORY_STRUCT(Zoo, (animal, fory::F(0).nullable()), // Auto-detect polymorphism (fixed_animal, fory::F(1).nullable().dynamic(false)), // Skip type info (polymorphic_data, fory::F(2).dynamic(true)) // Force type info @@ -286,9 +270,7 @@ struct Zoo { std::shared_ptr<Dog> maybe_mixed_breed; }; -FORY_STRUCT(Zoo, animal, maybe_mixed_breed); - -FORY_FIELD_CONFIG(Zoo, +FORY_STRUCT(Zoo, (animal, fory::F(0).nullable()), // Auto-detect (Animal is polymorphic) (maybe_mixed_breed, fory::F(1).dynamic(true)) // Force dynamic for concrete type ); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
