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.git


The following commit(s) were added to refs/heads/main by this push:
     new f340dd45f docs(c++): add c++ polymorphism doc (#3187)
f340dd45f is described below

commit f340dd45fa8d54be7e72688329dde27267aece87
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Jan 21 19:46:55 2026 +0800

    docs(c++): add c++ polymorphism doc (#3187)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    #2906
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 cpp/fory/serialization/map_serializer.h            |  27 +-
 cpp/fory/serialization/serializer_traits.h         |  22 -
 cpp/fory/serialization/smart_ptr_serializers.h     |  26 +-
 cpp/fory/serialization/struct_serializer.h         |  44 +-
 cpp/fory/serialization/weak_ptr_serializer.h       |   7 -
 cpp/fory/serialization/weak_ptr_serializer_test.cc |   6 +-
 docs/guide/cpp/cross-language.md                   |   2 +-
 docs/guide/cpp/custom-serializers.md               |   2 +-
 docs/guide/cpp/field-configuration.md              |  44 +-
 docs/guide/cpp/polymorphism.md                     | 480 +++++++++++++++++++++
 docs/guide/cpp/row-format.md                       |   2 +-
 docs/guide/cpp/supported-types.md                  |   2 +-
 12 files changed, 551 insertions(+), 113 deletions(-)

diff --git a/cpp/fory/serialization/map_serializer.h 
b/cpp/fory/serialization/map_serializer.h
index 8947b2773..cbeed0544 100644
--- a/cpp/fory/serialization/map_serializer.h
+++ b/cpp/fory/serialization/map_serializer.h
@@ -925,8 +925,7 @@ struct Serializer<std::map<K, V, Args...>> {
   static inline void write_data(const MapType &map, WriteContext &ctx) {
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       write_map_data_fast<K, V>(map, ctx, false);
@@ -939,8 +938,7 @@ struct Serializer<std::map<K, V, Args...>> {
                                         bool has_generics) {
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       write_map_data_fast<K, V>(map, ctx, has_generics);
@@ -978,8 +976,7 @@ struct Serializer<std::map<K, V, Args...>> {
 
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       return read_map_data_fast<K, V, MapType>(ctx, length);
@@ -1002,8 +999,7 @@ struct Serializer<std::map<K, V, Args...>> {
 
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       return read_map_data_fast<K, V, MapType>(ctx, length);
@@ -1032,8 +1028,7 @@ struct Serializer<std::unordered_map<K, V, Args...>> {
 
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       write_map_data_fast<K, V>(map, ctx, false);
@@ -1045,8 +1040,7 @@ struct Serializer<std::unordered_map<K, V, Args...>> {
   static inline void write_data(const MapType &map, WriteContext &ctx) {
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       write_map_data_fast<K, V>(map, ctx, false);
@@ -1059,8 +1053,7 @@ struct Serializer<std::unordered_map<K, V, Args...>> {
                                         bool has_generics) {
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       write_map_data_fast<K, V>(map, ctx, has_generics);
@@ -1098,8 +1091,7 @@ struct Serializer<std::unordered_map<K, V, Args...>> {
 
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       return read_map_data_fast<K, V, MapType>(ctx, length);
@@ -1122,8 +1114,7 @@ struct Serializer<std::unordered_map<K, V, Args...>> {
 
     constexpr bool is_fast_path =
         !is_polymorphic_v<K> && !is_polymorphic_v<V> && !is_shared_ref_v<K> &&
-        !is_shared_ref_v<V> && !requires_ref_metadata_v<K> &&
-        !requires_ref_metadata_v<V>;
+        !is_shared_ref_v<V> && !is_nullable_v<K> && !is_nullable_v<V>;
 
     if constexpr (is_fast_path) {
       return read_map_data_fast<K, V, MapType>(ctx, length);
diff --git a/cpp/fory/serialization/serializer_traits.h 
b/cpp/fory/serialization/serializer_traits.h
index 1b378ad8c..786777690 100644
--- a/cpp/fory/serialization/serializer_traits.h
+++ b/cpp/fory/serialization/serializer_traits.h
@@ -369,28 +369,6 @@ struct is_shared_ref<std::shared_ptr<T>> : std::true_type 
{};
 template <typename T>
 inline constexpr bool is_shared_ref_v = is_shared_ref<T>::value;
 
-// ============================================================================
-// Reference Metadata Requirements
-// ============================================================================
-
-/// Determine if a type requires reference metadata (null/ref flags) even when
-/// nested inside another structure.
-/// Note: std::string does NOT require ref metadata - it's written inline
-/// without ref tracking, matching Java/Rust xlang behavior.
-template <typename T> struct requires_ref_metadata : std::false_type {};
-
-template <typename T>
-struct requires_ref_metadata<std::optional<T>> : std::true_type {};
-
-template <typename T>
-struct requires_ref_metadata<std::shared_ptr<T>> : std::true_type {};
-
-template <typename T>
-struct requires_ref_metadata<std::unique_ptr<T>> : std::true_type {};
-
-template <typename T>
-inline constexpr bool requires_ref_metadata_v = 
requires_ref_metadata<T>::value;
-
 // ============================================================================
 // Element Type Extraction
 // ============================================================================
diff --git a/cpp/fory/serialization/smart_ptr_serializers.h 
b/cpp/fory/serialization/smart_ptr_serializers.h
index b581382b3..30e3df016 100644
--- a/cpp/fory/serialization/smart_ptr_serializers.h
+++ b/cpp/fory/serialization/smart_ptr_serializers.h
@@ -83,7 +83,7 @@ template <typename T> struct Serializer<std::optional<T>> {
   static inline void write(const std::optional<T> &opt, WriteContext &ctx,
                            RefMode ref_mode, bool write_type,
                            bool has_generics = false) {
-    constexpr bool inner_requires_ref = requires_ref_metadata_v<T>;
+    constexpr bool inner_is_nullable = is_nullable_v<T>;
 
     if (ref_mode == RefMode::None) {
       if (!opt.has_value()) {
@@ -100,7 +100,7 @@ template <typename T> struct Serializer<std::optional<T>> {
       return;
     }
 
-    if constexpr (inner_requires_ref) {
+    if constexpr (inner_is_nullable) {
       Serializer<T>::write(*opt, ctx, RefMode::NullOnly, write_type,
                            has_generics);
     } else {
@@ -131,7 +131,7 @@ template <typename T> struct Serializer<std::optional<T>> {
 
   static inline std::optional<T> read(ReadContext &ctx, RefMode ref_mode,
                                       bool read_type) {
-    constexpr bool inner_requires_ref = requires_ref_metadata_v<T>;
+    constexpr bool inner_is_nullable = is_nullable_v<T>;
 
     std::cerr << "[optional::read] T=" << typeid(T).name()
               << ", ref_mode=" << static_cast<int>(ref_mode)
@@ -155,7 +155,7 @@ template <typename T> struct Serializer<std::optional<T>> {
       return std::optional<T>(std::nullopt);
     }
 
-    if constexpr (inner_requires_ref) {
+    if constexpr (inner_is_nullable) {
       // Rewind so the inner serializer can consume the reference metadata.
       ctx.buffer().ReaderIndex(flag_pos);
       // Pass ref_mode directly - let inner serializer handle ref tracking
@@ -183,7 +183,7 @@ template <typename T> struct Serializer<std::optional<T>> {
   static inline std::optional<T>
   read_with_type_info(ReadContext &ctx, RefMode ref_mode,
                       const TypeInfo &type_info) {
-    constexpr bool inner_requires_ref = requires_ref_metadata_v<T>;
+    constexpr bool inner_is_nullable = is_nullable_v<T>;
 
     if (ref_mode == RefMode::None) {
       T value =
@@ -204,7 +204,7 @@ template <typename T> struct Serializer<std::optional<T>> {
       return std::optional<T>(std::nullopt);
     }
 
-    if constexpr (inner_requires_ref) {
+    if constexpr (inner_is_nullable) {
       // Rewind so the inner serializer can consume the reference metadata.
       ctx.buffer().ReaderIndex(flag_pos);
       // Pass ref_mode directly - let inner serializer handle ref tracking
@@ -264,9 +264,10 @@ template <typename T> struct SharedPtrTypeIdHelper<T, 
true> {
 template <typename T> struct Serializer<std::shared_ptr<T>> {
   static_assert(!std::is_pointer_v<T>,
                 "shared_ptr of raw pointer types is not supported");
-  static_assert(!requires_ref_metadata_v<T>,
-                "shared_ptr of nullable types (optional/shared_ptr/unique_ptr) 
"
-                "is not supported. Use the wrapper type directly instead.");
+  static_assert(!is_nullable_v<T>,
+                "shared_ptr of nullable types "
+                "(optional/shared_ptr/unique_ptr/weak_ptr) is not supported. "
+                "Use the wrapper type directly instead.");
 
   static constexpr TypeId type_id =
       SharedPtrTypeIdHelper<T, std::is_polymorphic_v<T>>::value;
@@ -748,9 +749,10 @@ template <typename T> struct UniquePtrTypeIdHelper<T, 
true> {
 template <typename T> struct Serializer<std::unique_ptr<T>> {
   static_assert(!std::is_pointer_v<T>,
                 "unique_ptr of raw pointer types is not supported");
-  static_assert(!requires_ref_metadata_v<T>,
-                "unique_ptr of nullable types (optional/shared_ptr/unique_ptr) 
"
-                "is not supported. Use the wrapper type directly instead.");
+  static_assert(!is_nullable_v<T>,
+                "unique_ptr of nullable types "
+                "(optional/shared_ptr/unique_ptr/weak_ptr) is not supported. "
+                "Use the wrapper type directly instead.");
 
   static constexpr TypeId type_id =
       UniquePtrTypeIdHelper<T, std::is_polymorphic_v<T>>::value;
diff --git a/cpp/fory/serialization/struct_serializer.h 
b/cpp/fory/serialization/struct_serializer.h
index bff69cc02..e0fa60065 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -699,9 +699,10 @@ template <typename T> struct CompileTimeFieldHelpers {
   template <size_t Index>
   using UnwrappedFieldType = typename UnwrappedFieldTypeHelper<Index>::type;
 
-  /// Legacy compatibility: returns true if field requires ref metadata
-  /// in the wire format (i.e., is optional/nullable)
-  template <size_t Index> static constexpr bool field_requires_ref_metadata() {
+  /// Returns true if the field's type can hold null (optional/shared_ptr/
+  /// unique_ptr/weak_ptr). This forces ref/null flags in the wire format even
+  /// when field metadata marks it non-nullable.
+  template <size_t Index> static constexpr bool field_type_is_nullable() {
     if constexpr (FieldCount == 0) {
       return false;
     } else {
@@ -709,7 +710,7 @@ template <typename T> struct CompileTimeFieldHelpers {
       using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
       using FieldType = unwrap_field_t<RawFieldType>;
       // Check the unwrapped type
-      return requires_ref_metadata_v<FieldType>;
+      return is_nullable_v<FieldType>;
     }
   }
 
@@ -877,11 +878,11 @@ template <typename T> struct CompileTimeFieldHelpers {
 
   template <size_t... Indices>
   static constexpr std::array<bool, FieldCount>
-  make_requires_ref_metadata_flags(std::index_sequence<Indices...>) {
+  make_nullable_type_flags(std::index_sequence<Indices...>) {
     if constexpr (FieldCount == 0) {
       return {};
     } else {
-      return {field_requires_ref_metadata<Indices>()...};
+      return {field_type_is_nullable<Indices>()...};
     }
   }
 
@@ -891,11 +892,10 @@ template <typename T> struct CompileTimeFieldHelpers {
   static inline constexpr std::array<bool, FieldCount> nullable_flags =
       make_nullable_flags(std::make_index_sequence<FieldCount>{});
 
-  /// Flags for fields that require ref metadata encoding (smart pointers,
-  /// optional)
-  static inline constexpr std::array<bool, FieldCount>
-      requires_ref_metadata_flags = make_requires_ref_metadata_flags(
-          std::make_index_sequence<FieldCount>{});
+  /// Flags for fields whose types are nullable wrappers (optional/shared_ptr/
+  /// unique_ptr/weak_ptr), which require ref/null flags in the wire format.
+  static inline constexpr std::array<bool, FieldCount> nullable_type_flags =
+      make_nullable_type_flags(std::make_index_sequence<FieldCount>{});
 
   static inline constexpr std::array<size_t, FieldCount> snake_case_lengths =
       []() constexpr {
@@ -1123,7 +1123,7 @@ template <typename T> struct CompileTimeFieldHelpers {
     } else {
       for (size_t i = 0; i < FieldCount; ++i) {
         if (!is_primitive_type_id(type_ids[i]) || nullable_flags[i] ||
-            requires_ref_metadata_flags[i]) {
+            nullable_type_flags[i]) {
           return false;
         }
       }
@@ -1198,7 +1198,7 @@ template <typename T> struct CompileTimeFieldHelpers {
         size_t original_idx = sorted_indices[i];
         if (is_primitive_type_id(type_ids[original_idx]) &&
             !nullable_flags[original_idx] &&
-            !requires_ref_metadata_flags[original_idx]) {
+            !nullable_type_flags[original_idx]) {
           ++count;
         } else {
           break; // Non-nullable primitives are always first in sorted order
@@ -1644,8 +1644,8 @@ void write_single_field(const T &obj, WriteContext &ctx,
   // Get field metadata from fory::field<> or FORY_FIELD_TAGS or defaults
   constexpr bool is_nullable = Helpers::template field_nullable<Index>();
   constexpr bool track_ref = Helpers::template field_track_ref<Index>();
-  // For backwards compatibility, also check requires_ref_metadata_v
-  constexpr bool field_requires_ref = requires_ref_metadata_v<FieldType>;
+  // Some wrapper types always require ref/null flags in the wire format.
+  constexpr bool field_type_is_nullable = is_nullable_v<FieldType>;
 
   // Special handling for std::optional<uint32_t/uint64_t> with encoding config
   // This must come BEFORE the general primitive check because optional 
requires
@@ -1725,7 +1725,7 @@ void write_single_field(const T &obj, WriteContext &ctx,
   }
 
   // Per Rust implementation: primitives are written directly without ref/type
-  if constexpr (is_primitive_field && !field_requires_ref) {
+  if constexpr (is_primitive_field && !field_type_is_nullable) {
     if constexpr (::fory::detail::has_field_config_v<T> &&
                   (std::is_same_v<FieldType, uint32_t> ||
                    std::is_same_v<FieldType, uint64_t> ||
@@ -1785,7 +1785,7 @@ void write_single_field(const T &obj, WriteContext &ctx,
   if constexpr (is_collection_field) {
     // Compute RefMode from field metadata
     constexpr RefMode coll_ref_mode =
-        make_ref_mode(is_nullable || field_requires_ref, track_ref);
+        make_ref_mode(is_nullable || field_type_is_nullable, track_ref);
     Serializer<FieldType>::write(field_value, ctx, coll_ref_mode, false, true);
     return;
   }
@@ -1794,7 +1794,7 @@ void write_single_field(const T &obj, WriteContext &ctx,
   // RefMode: based on nullable and track_ref flags
   // Per xlang protocol: non-nullable fields skip ref flag entirely
   constexpr RefMode field_ref_mode =
-      make_ref_mode(is_nullable || field_requires_ref, track_ref);
+      make_ref_mode(is_nullable || field_type_is_nullable, track_ref);
 
   // write_type: determined by field_need_write_type_info logic
   // Enums: false (per Rust util.rs:58-59)
@@ -2031,7 +2031,7 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) 
{
   // mode, nested structs carry TypeMeta in the stream so that
   // `Serializer<T>::read` can dispatch to `read_compatible` with the correct
   // remote schema.
-  constexpr bool field_requires_ref = requires_ref_metadata_v<FieldType>;
+  constexpr bool field_type_is_nullable = is_nullable_v<FieldType>;
   constexpr TypeId field_type_id = Serializer<FieldType>::type_id;
   // Check if field is a struct type - use type_id to handle shared_ptr<Struct>
   constexpr bool is_struct_field = is_struct_type(field_type_id);
@@ -2072,13 +2072,13 @@ void read_single_field_by_index(T &obj, ReadContext 
&ctx) {
   // RefMode: based on nullable and track_ref flags
   // Per xlang protocol: non-nullable fields skip ref flag entirely
   constexpr RefMode field_ref_mode =
-      make_ref_mode(is_nullable || field_requires_ref, track_ref);
+      make_ref_mode(is_nullable || field_type_is_nullable, track_ref);
 
 #ifdef ENABLE_FORY_DEBUG_OUTPUT
   const auto debug_names = decltype(field_info)::Names;
   std::cerr << "[xlang][field] T=" << typeid(T).name() << ", index=" << Index
             << ", name=" << debug_names[Index]
-            << ", field_requires_ref=" << field_requires_ref
+            << ", field_type_is_nullable=" << field_type_is_nullable
             << ", is_nullable=" << is_nullable
             << ", ref_mode=" << static_cast<int>(field_ref_mode)
             << ", read_type=" << read_type
@@ -2089,7 +2089,7 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) 
{
   // shared_ptr) that don't need ref metadata, bypass Serializer<T>::read
   // and use direct buffer reads with Error&.
   constexpr bool is_raw_prim = is_raw_primitive_v<FieldType>;
-  if constexpr (is_raw_prim && is_primitive_field && !field_requires_ref) {
+  if constexpr (is_raw_prim && is_primitive_field && !field_type_is_nullable) {
     auto read_value = [&ctx]() -> FieldType {
       if constexpr (is_configurable_int_v<FieldType>) {
 #ifdef ENABLE_FORY_DEBUG_OUTPUT
diff --git a/cpp/fory/serialization/weak_ptr_serializer.h 
b/cpp/fory/serialization/weak_ptr_serializer.h
index 6f44d57ea..95eaa801e 100644
--- a/cpp/fory/serialization/weak_ptr_serializer.h
+++ b/cpp/fory/serialization/weak_ptr_serializer.h
@@ -182,13 +182,6 @@ template <typename T> struct TypeIndex<SharedWeak<T>> {
       fnv1a_64_combine(fnv1a_64("fory::SharedWeak"), type_index<T>());
 };
 
-// ============================================================================
-// requires_ref_metadata trait for SharedWeak<T>
-// ============================================================================
-
-template <typename T>
-struct requires_ref_metadata<SharedWeak<T>> : std::true_type {};
-
 // ============================================================================
 // is_nullable trait for SharedWeak<T>
 // ============================================================================
diff --git a/cpp/fory/serialization/weak_ptr_serializer_test.cc 
b/cpp/fory/serialization/weak_ptr_serializer_test.cc
index 4d6ed7d19..112cfc38d 100644
--- a/cpp/fory/serialization/weak_ptr_serializer_test.cc
+++ b/cpp/fory/serialization/weak_ptr_serializer_test.cc
@@ -447,9 +447,9 @@ TEST(WeakPtrSerializerTest, RequiresTrackRef) {
 // ============================================================================
 
 TEST(WeakPtrSerializerTest, TypeTraits) {
-  // Test requires_ref_metadata
-  EXPECT_TRUE(requires_ref_metadata_v<SharedWeak<int32_t>>);
-  EXPECT_TRUE(requires_ref_metadata_v<SharedWeak<SimpleStruct>>);
+  // Test is_nullable
+  EXPECT_TRUE(is_nullable_v<SharedWeak<int32_t>>);
+  EXPECT_TRUE(is_nullable_v<SharedWeak<SimpleStruct>>);
 
   // Test is_nullable
   EXPECT_TRUE(is_nullable_v<SharedWeak<int32_t>>);
diff --git a/docs/guide/cpp/cross-language.md b/docs/guide/cpp/cross-language.md
index 3d0c8b1ea..ce192de8b 100644
--- a/docs/guide/cpp/cross-language.md
+++ b/docs/guide/cpp/cross-language.md
@@ -1,6 +1,6 @@
 ---
 title: Cross-Language Serialization
-sidebar_position: 7
+sidebar_position: 10
 id: cross_language
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/docs/guide/cpp/custom-serializers.md 
b/docs/guide/cpp/custom-serializers.md
index 187105f50..c17ed0358 100644
--- a/docs/guide/cpp/custom-serializers.md
+++ b/docs/guide/cpp/custom-serializers.md
@@ -1,6 +1,6 @@
 ---
 title: Custom Serializers
-sidebar_position: 4
+sidebar_position: 6
 id: custom_serializers
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/docs/guide/cpp/field-configuration.md 
b/docs/guide/cpp/field-configuration.md
index 991ed5b22..7469f7bf1 100644
--- a/docs/guide/cpp/field-configuration.md
+++ b/docs/guide/cpp/field-configuration.md
@@ -1,6 +1,6 @@
 ---
 title: Field Configuration
-sidebar_position: 5
+sidebar_position: 7
 id: field_configuration
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
@@ -23,16 +23,31 @@ This page explains how to configure field-level metadata 
for serialization.
 
 ## Overview
 
-Apache Fory™ provides two ways to specify field-level metadata at compile time:
+Apache Fory™ provides three ways to configure field-level metadata at compile 
time:
 
-1. **`fory::field<>` template** - Inline metadata in struct definition
-2. **`FORY_FIELD_TAGS` macro** - Non-invasive metadata added separately
+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
 
@@ -303,16 +318,6 @@ FORY_FIELD_TAGS(Document,
 | `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)`                
                                   |
 
-### API Comparison
-
-| Aspect                  | `fory::field<>` Wrapper  | `FORY_FIELD_TAGS` Macro 
|
-| ----------------------- | ------------------------ | ----------------------- 
|
-| **Struct definition**   | Modified (wrapped types) | Unchanged (pure C++)    
|
-| **IDE support**         | Template noise           | Excellent (clean types) 
|
-| **Third-party classes** | Not supported            | Supported               
|
-| **Header dependencies** | Required everywhere      | Isolated to config      
|
-| **Migration effort**    | High (change all fields) | Low (add one macro)     
|
-
 ## FORY_FIELD_CONFIG Macro
 
 The `FORY_FIELD_CONFIG` macro is the most powerful and flexible way to 
configure field-level serialization. It provides:
@@ -516,17 +521,6 @@ FORY_FIELD_CONFIG(DataV2,
 | `.tagged()`       | Use tagged hybrid encoding                       | 
`uint64_t` only            |
 | `.compress(v)`    | Enable/disable field compression                 | All 
types                  |
 
-### Comparing Field Configuration Macros
-
-| 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    
                   |
-| **Compile-time verify** | Yes                   | Limited           | Yes 
(member pointers)     |
-| **Cross-lang compat**   | Limited               | Limited           | Full   
                   |
-| **Recommended for**     | Simple structs        | Third-party types | 
Complex/xlang structs     |
-
 ## Default Values
 
 - **Nullable**: Only `std::optional<T>` is nullable by default; all other 
types (including `std::shared_ptr`) are non-nullable
diff --git a/docs/guide/cpp/polymorphism.md b/docs/guide/cpp/polymorphism.md
new file mode 100644
index 000000000..97f5bad11
--- /dev/null
+++ b/docs/guide/cpp/polymorphism.md
@@ -0,0 +1,480 @@
+---
+title: Polymorphic Serialization
+sidebar_position: 5
+id: polymorphism
+license: |
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+---
+
+Apache Fory™ supports polymorphic serialization through smart pointers 
(`std::shared_ptr` and `std::unique_ptr`), enabling dynamic dispatch and type 
flexibility for inheritance hierarchies.
+
+## Supported Polymorphic Types
+
+- `std::shared_ptr<Base>` - Shared ownership with polymorphic dispatch
+- `std::unique_ptr<Base>` - Exclusive ownership with polymorphic dispatch
+- Collections: `std::vector<std::shared_ptr<Base>>`, `std::map<K, 
std::unique_ptr<Base>>`
+- Optional: `std::optional<std::shared_ptr<Base>>`
+
+## Basic Polymorphic Serialization
+
+```cpp
+#include "fory/serialization/fory.h"
+
+using namespace fory::serialization;
+
+// Define base class with virtual methods
+struct Animal {
+  virtual ~Animal() = default;
+  virtual std::string speak() const = 0;
+  int32_t age = 0;
+};
+FORY_STRUCT(Animal, age);
+
+// Define derived classes
+struct Dog : Animal {
+  std::string speak() const override { return "Woof!"; }
+  std::string breed;
+};
+FORY_STRUCT(Dog, age, breed);
+
+struct Cat : Animal {
+  std::string speak() const override { return "Meow!"; }
+  std::string color;
+};
+FORY_STRUCT(Cat, age, color);
+
+// Struct with polymorphic field
+struct Zoo {
+  std::shared_ptr<Animal> star_animal;
+};
+FORY_STRUCT(Zoo, star_animal);
+
+int main() {
+  auto fory = Fory::builder().track_ref(true).build();
+
+  // Register all types with unique type IDs
+  fory.register_struct<Zoo>(100);
+  fory.register_struct<Dog>(101);
+  fory.register_struct<Cat>(102);
+
+  // Create object with polymorphic field
+  Zoo zoo;
+  zoo.star_animal = std::make_shared<Dog>();
+  zoo.star_animal->age = 3;
+  static_cast<Dog*>(zoo.star_animal.get())->breed = "Labrador";
+
+  // Serialize
+  auto bytes_result = fory.serialize(zoo);
+  assert(bytes_result.ok());
+
+  // Deserialize - runtime type is preserved
+  auto decoded_result = fory.deserialize<Zoo>(bytes_result.value());
+  assert(decoded_result.ok());
+
+  auto decoded = std::move(decoded_result).value();
+  assert(decoded.star_animal->speak() == "Woof!");
+  assert(decoded.star_animal->age == 3);
+
+  auto* dog_ptr = dynamic_cast<Dog*>(decoded.star_animal.get());
+  assert(dog_ptr != nullptr);
+  assert(dog_ptr->breed == "Labrador");
+}
+```
+
+## Type Registration for Polymorphism
+
+For polymorphic serialization, register derived types with unique type IDs:
+
+```cpp
+// Register with numeric type ID
+fory.register_struct<Derived1>(100);
+fory.register_struct<Derived2>(101);
+```
+
+**Why type ID registration?**
+
+- Compact binary representation
+- Fast type lookup and dispatch
+- Consistent with non-polymorphic type registration
+
+## Automatic Polymorphism Detection
+
+Fory automatically detects polymorphic types using `std::is_polymorphic<T>`:
+
+```cpp
+struct Base {
+  virtual ~Base() = default;  // Virtual destructor makes it polymorphic
+  int32_t value = 0;
+};
+
+struct NonPolymorphic {
+  int32_t value = 0;  // No virtual methods
+};
+
+// Polymorphic field - type info written automatically
+struct Container1 {
+  std::shared_ptr<Base> ptr;  // Auto-detected as polymorphic
+};
+
+// Non-polymorphic field - no type info written
+struct Container2 {
+  std::shared_ptr<NonPolymorphic> ptr;  // Not polymorphic
+};
+```
+
+## Controlling Dynamic Dispatch
+
+Use `fory::dynamic<V>` to override automatic polymorphism detection:
+
+```cpp
+struct Animal {
+  virtual ~Animal() = default;
+  virtual std::string speak() const = 0;
+};
+
+struct Pet {
+  // Auto-detected: type info written (Animal has virtual methods)
+  std::shared_ptr<Animal> animal1;
+
+  // Force dynamic: type info written explicitly
+  fory::field<std::shared_ptr<Animal>, 0, fory::dynamic<true>> animal2;
+
+  // Force non-dynamic: skip type info (faster but no runtime subtyping)
+  fory::field<std::shared_ptr<Animal>, 1, fory::dynamic<false>> animal3;
+};
+FORY_STRUCT(Pet, animal1, animal2, animal3);
+```
+
+**When to use `fory::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
+
+Use `FORY_FIELD_CONFIG` to configure fields without `fory::field<>` wrapper:
+
+```cpp
+struct Zoo {
+  std::shared_ptr<Animal> star;      // Auto-detected as polymorphic
+  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
+);
+```
+
+See [Field Configuration](field-configuration.md) for complete details on 
`fory::nullable`, `fory::ref`, and other field-level options
+
+## std::unique_ptr Polymorphism
+
+`std::unique_ptr` works the same way as `std::shared_ptr` for polymorphic 
types:
+
+```cpp
+struct Container {
+  std::unique_ptr<Animal> pet;
+};
+FORY_STRUCT(Container, pet);
+
+auto fory = Fory::builder().track_ref(true).build();
+fory.register_struct<Container>(200);
+fory.register_struct<Dog>(201);
+
+Container container;
+container.pet = std::make_unique<Dog>();
+static_cast<Dog*>(container.pet.get())->breed = "Beagle";
+
+auto bytes = fory.serialize(container).value();
+auto decoded = fory.deserialize<Container>(bytes).value();
+
+// Runtime type preserved
+auto* dog = dynamic_cast<Dog*>(decoded.pet.get());
+assert(dog != nullptr);
+assert(dog->breed == "Beagle");
+```
+
+## Collections of Polymorphic Objects
+
+```cpp
+#include <vector>
+#include <map>
+
+struct AnimalShelter {
+  std::vector<std::shared_ptr<Animal>> animals;
+  std::map<std::string, std::unique_ptr<Animal>> registry;
+};
+FORY_STRUCT(AnimalShelter, animals, registry);
+
+auto fory = Fory::builder().track_ref(true).build();
+fory.register_struct<AnimalShelter>(100);
+fory.register_struct<Dog>(101);
+fory.register_struct<Cat>(102);
+
+AnimalShelter shelter;
+shelter.animals.push_back(std::make_shared<Dog>());
+shelter.animals.push_back(std::make_shared<Cat>());
+shelter.registry["pet1"] = std::make_unique<Dog>();
+
+auto bytes = fory.serialize(shelter).value();
+auto decoded = fory.deserialize<AnimalShelter>(bytes).value();
+
+// All runtime types preserved
+assert(dynamic_cast<Dog*>(decoded.animals[0].get()) != nullptr);
+assert(dynamic_cast<Cat*>(decoded.animals[1].get()) != nullptr);
+assert(dynamic_cast<Dog*>(decoded.registry["pet1"].get()) != nullptr);
+```
+
+## Reference Tracking
+
+Reference tracking for `std::shared_ptr` works the same with polymorphic types.
+See [Supported Types](supported-types.md) for details and examples.
+
+## Nested Polymorphism Depth Limit
+
+To prevent stack overflow from deeply nested polymorphic structures, Fory 
limits the maximum dynamic nesting depth:
+
+```cpp
+struct Container {
+  virtual ~Container() = default;
+  int32_t value = 0;
+  std::shared_ptr<Container> nested;
+};
+FORY_STRUCT(Container, value, nested);
+
+// Default max_dyn_depth is 5
+auto fory1 = Fory::builder().build();
+assert(fory1.config().max_dyn_depth == 5);
+
+// Increase limit for deeper nesting
+auto fory2 = Fory::builder().max_dyn_depth(10).build();
+fory2.register_struct<Container>(1);
+
+// Create deeply nested structure
+auto level3 = std::make_shared<Container>();
+level3->value = 3;
+
+auto level2 = std::make_shared<Container>();
+level2->value = 2;
+level2->nested = level3;
+
+auto level1 = std::make_shared<Container>();
+level1->value = 1;
+level1->nested = level2;
+
+// Serialization succeeds
+auto bytes = fory2.serialize(level1).value();
+
+// Deserialization succeeds with sufficient depth
+auto decoded = fory2.deserialize<std::shared_ptr<Container>>(bytes).value();
+```
+
+**Depth exceeded error:**
+
+```cpp
+auto fory_shallow = Fory::builder().max_dyn_depth(2).build();
+fory_shallow.register_struct<Container>(1);
+
+// 3 levels exceeds max_dyn_depth=2
+auto result = fory_shallow.deserialize<std::shared_ptr<Container>>(bytes);
+assert(!result.ok());  // Fails with depth exceeded error
+```
+
+**When to adjust:**
+
+- **Increase `max_dyn_depth`**: For legitimate deeply nested polymorphic data 
structures
+- **Decrease `max_dyn_depth`**: For stricter security requirements or shallow 
data structures
+
+## 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`.
+
+```cpp
+struct Pet {
+  // Non-nullable (default)
+  std::shared_ptr<Animal> primary;
+
+  // Nullable via explicit field metadata
+  fory::field<std::shared_ptr<Animal>, 0, fory::nullable> optional;
+};
+FORY_STRUCT(Pet, primary, optional);
+```
+
+See [Field Configuration](field-configuration.md) for more details.
+
+## Combining Polymorphism with Other Features
+
+### Polymorphism + Reference Tracking
+
+```cpp
+struct GraphNode {
+  virtual ~GraphNode() = default;
+  int32_t id = 0;
+  std::vector<std::shared_ptr<GraphNode>> neighbors;
+};
+FORY_STRUCT(GraphNode, id, neighbors);
+
+struct WeightedNode : GraphNode {
+  double weight = 0.0;
+};
+FORY_STRUCT(WeightedNode, id, neighbors, weight);
+
+// Enable ref tracking to handle shared references and cycles
+auto fory = Fory::builder().track_ref(true).build();
+fory.register_struct<GraphNode>(100);
+fory.register_struct<WeightedNode>(101);
+
+// Create cyclic graph
+auto node1 = std::make_shared<WeightedNode>();
+node1->id = 1;
+
+auto node2 = std::make_shared<WeightedNode>();
+node2->id = 2;
+
+node1->neighbors.push_back(node2);
+node2->neighbors.push_back(node1);  // Cycle
+
+auto bytes = fory.serialize(node1).value();
+auto decoded = fory.deserialize<std::shared_ptr<GraphNode>>(bytes).value();
+// Cycle handled correctly
+```
+
+### Polymorphism + Schema Evolution
+
+Use compatible mode for schema evolution with polymorphic types:
+
+```cpp
+auto fory = Fory::builder()
+    .compatible(true)  // Enable schema evolution
+    .track_ref(true)
+    .build();
+```
+
+## Best Practices
+
+1. **Use type ID registration** for polymorphic types:
+
+   ```cpp
+   fory.register_struct<DerivedType>(100);
+   ```
+
+2. **Enable reference tracking** for polymorphic types:
+
+   ```cpp
+   auto fory = Fory::builder().track_ref(true).build();
+   ```
+
+3. **Virtual destructors required**: Ensure base classes have virtual 
destructors:
+
+   ```cpp
+   struct Base {
+     virtual ~Base() = default;  // Required for polymorphism
+   };
+   ```
+
+4. **Register all concrete types** before serialization/deserialization:
+
+   ```cpp
+   fory.register_struct<Derived1>(100);
+   fory.register_struct<Derived2>(101);
+   ```
+
+5. **Use `dynamic_cast`** to downcast after deserialization:
+
+   ```cpp
+   auto* derived = dynamic_cast<DerivedType*>(base_ptr.get());
+   if (derived) {
+     // Use derived-specific members
+   }
+   ```
+
+6. **Adjust `max_dyn_depth`** based on your data structure depth:
+
+   ```cpp
+   auto fory = Fory::builder().max_dyn_depth(10).build();
+   ```
+
+7. **Use `fory::nullable`** for optional polymorphic fields:
+
+   ```cpp
+   fory::field<std::shared_ptr<Base>, 0, fory::nullable> optional_ptr;
+   ```
+
+## Error Handling
+
+```cpp
+auto bytes_result = fory.serialize(obj);
+if (!bytes_result.ok()) {
+  std::cerr << "Serialization failed: "
+            << bytes_result.error().to_string() << std::endl;
+  return;
+}
+
+auto decoded_result = fory.deserialize<MyType>(bytes_result.value());
+if (!decoded_result.ok()) {
+  std::cerr << "Deserialization failed: "
+            << decoded_result.error().to_string() << std::endl;
+  return;
+}
+```
+
+**Common errors:**
+
+- **Type not registered**: Register all concrete types with unique IDs before 
use
+- **Depth exceeded**: Increase `max_dyn_depth` for deeply nested structures
+- **Type ID conflict**: Ensure each type has a unique type ID across all 
registered types
+
+## Performance Considerations
+
+**Polymorphic serialization overhead:**
+
+- Type metadata written for each polymorphic object (~16-32 bytes)
+- Dynamic type resolution during deserialization
+- Virtual function calls for runtime dispatch
+
+**Optimization tips:**
+
+1. **Use `fory::dynamic<false>`** when runtime type matches declared type:
+
+   ```cpp
+   fory::field<std::shared_ptr<Base>, 0, fory::dynamic<false>> fixed_type;
+   ```
+
+2. **Minimize nesting depth** to reduce metadata overhead
+
+3. **Batch polymorphic objects** in collections rather than individual fields
+
+4. **Consider non-polymorphic alternatives** when polymorphism isn't needed:
+
+   ```cpp
+   std::variant<Dog, Cat> animal;  // Type-safe union instead of polymorphism
+   ```
+
+## Related Topics
+
+- [Type Registration](type-registration.md) - Registering types for 
serialization
+- [Field Configuration](field-configuration.md) - Field-level metadata and 
options
+- [Supported Types](supported-types.md) - Smart pointers and collections
+- [Configuration](configuration.md) - `max_dyn_depth` and other settings
+- [Basic Serialization](basic-serialization.md) - Core serialization concepts
diff --git a/docs/guide/cpp/row-format.md b/docs/guide/cpp/row-format.md
index 672770bcf..a3638ab7c 100644
--- a/docs/guide/cpp/row-format.md
+++ b/docs/guide/cpp/row-format.md
@@ -1,6 +1,6 @@
 ---
 title: Row Format
-sidebar_position: 8
+sidebar_position: 20
 id: row_format
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/docs/guide/cpp/supported-types.md 
b/docs/guide/cpp/supported-types.md
index ed4d8e674..fb020e00a 100644
--- a/docs/guide/cpp/supported-types.md
+++ b/docs/guide/cpp/supported-types.md
@@ -1,6 +1,6 @@
 ---
 title: Supported Types
-sidebar_position: 6
+sidebar_position: 8
 id: supported_types
 license: |
   Licensed to the Apache Software Foundation (ASF) under one or more


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to