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]