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]


Reply via email to