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 c315ccf67 feat(): add c++ user guide doc (#3037)
c315ccf67 is described below
commit c315ccf67287150f79850902c4a1751d21e57185
Author: Shawn Yang <[email protected]>
AuthorDate: Thu Dec 11 09:28:20 2025 +0800
feat(): add c++ user guide doc (#3037)
## Why?
## What does this PR do?
## Related issues
Closes #3038
#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/README.md | 382 +++++++++++++++++++-
cpp/fory/serialization/BUILD | 1 +
cpp/fory/serialization/CMakeLists.txt | 1 +
cpp/fory/serialization/basic_serializer.h | 243 ++++++++-----
cpp/fory/serialization/serialization_test.cc | 30 ++
cpp/fory/serialization/serializer.h | 1 +
cpp/fory/serialization/string_serializer.h | 421 ++++++++++++++++++++++
cpp/fory/type/type.h | 6 +
docs/guide/cpp/basic-serialization.md | 238 ++++++++++++
docs/guide/cpp/configuration.md | 189 ++++++++++
docs/guide/cpp/cross-language.md | 271 ++++++++++++++
docs/guide/cpp/index.md | 245 +++++++++++++
docs/guide/cpp/row-format.md | 516 +++++++++++++++++++++++++++
docs/guide/cpp/schema-evolution.md | 404 +++++++++++++++++++++
docs/guide/cpp/supported-types.md | 277 ++++++++++++++
docs/guide/cpp/type-registration.md | 225 ++++++++++++
16 files changed, 3354 insertions(+), 96 deletions(-)
diff --git a/cpp/README.md b/cpp/README.md
index 96ec45647..5eab2ebd9 100644
--- a/cpp/README.md
+++ b/cpp/README.md
@@ -1,24 +1,392 @@
# Apache Fory™ C++
-Apache Fory™ is a blazingly-fast multi-language serialization framework
powered by just-in-time compilation and zero-copy.
+[](https://github.com/apache/fory/blob/main/LICENSE)
+
+**Apache Fory™** is a blazing fast multi-language serialization framework
powered by **JIT compilation** and **zero-copy** techniques, providing up to
**ultra-fast performance** while maintaining ease of use and type safety.
+
+The C++ implementation provides high-performance serialization with
compile-time type safety through template metaprogramming and zero-copy row
format for analytics workloads.
+
+## Why Apache Fory™ C++?
+
+- **Blazingly Fast**: Fast serialization and optimized binary protocols
+- **Cross-Language**: Seamlessly serialize/deserialize data across Java,
Python, C++, Go, JavaScript, and Rust
+- **Type-Safe**: Compile-time type checking with template specialization
+- **Shared References**: Automatic tracking of shared and circular references
+- **Schema Evolution**: Compatible mode for independent schema changes
+- **Two Modes**: Object graph serialization and zero-copy row-based format
+- **Modern C++17**: Clean API using modern C++ features
+
+## Quick Start
+
+### Basic Example
+
+```cpp
+#include "fory/serialization/fory.h"
+
+// Define your struct with FORY_STRUCT macro
+struct Person {
+ std::string name;
+ int32_t age;
+ std::string email;
+ FORY_STRUCT(Person, name, age, email);
+};
+
+int main() {
+ // Create Fory instance
+ auto fory = apache::fory::ForyBuilder()
+ .xlang(true)
+ .track_ref(true)
+ .build();
+
+ // Register type
+ fory->register_type<Person>(1);
+
+ // Create object
+ Person person{"Alice", 30, "[email protected]"};
+
+ // Serialize
+ auto result = fory->serialize(person);
+ if (!result.ok()) {
+ std::cerr << "Serialization failed: " << result.error().message() <<
std::endl;
+ return 1;
+ }
+ std::vector<uint8_t> bytes = std::move(result.value());
+
+ // Deserialize
+ auto decoded = fory->deserialize<Person>(bytes);
+ if (!decoded.ok()) {
+ std::cerr << "Deserialization failed: " << decoded.error().message() <<
std::endl;
+ return 1;
+ }
+ Person restored = decoded.value();
+
+ assert(restored.name == "Alice");
+ assert(restored.age == 30);
+ return 0;
+}
+```
+
+## Core Features
+
+### 1. Object Graph Serialization
+
+Apache Fory™ provides automatic serialization of complex object graphs,
preserving the structure and relationships between objects. The `FORY_STRUCT`
macro enables compile-time reflection without runtime overhead.
+
+**Key capabilities:**
+
+- Nested struct serialization with arbitrary depth
+- Collection types (vector, set, unordered_set)
+- Map types (map, unordered_map)
+- Optional fields with `std::optional<T>`
+- Smart pointers (shared_ptr, unique_ptr, weak_ptr)
+- Efficient binary encoding with variable-length integers
+
+```cpp
+#include "fory/serialization/fory.h"
+
+struct Address {
+ std::string street;
+ std::string city;
+ std::string country;
+ FORY_STRUCT(Address, street, city, country);
+};
+
+struct Person {
+ std::string name;
+ int32_t age;
+ Address address;
+ std::vector<std::string> hobbies;
+ std::map<std::string, std::string> metadata;
+ FORY_STRUCT(Person, name, age, address, hobbies, metadata);
+};
+
+auto fory = apache::fory::ForyBuilder().build();
+fory->register_type<Address>(100);
+fory->register_type<Person>(200);
+
+Person person{
+ "John Doe",
+ 30,
+ Address{"123 Main St", "New York", "USA"},
+ {"reading", "coding"},
+ {{"role", "developer"}}
+};
+
+auto bytes = fory->serialize(person).value();
+auto decoded = fory->deserialize<Person>(bytes).value();
+```
+
+### 2. Shared References
+
+Apache Fory™ automatically tracks and preserves reference identity for shared
objects using `std::shared_ptr<T>`. When the same object is referenced multiple
times, Fory serializes it only once and uses reference IDs for subsequent
occurrences.
+
+```cpp
+auto fory = apache::fory::ForyBuilder()
+ .track_ref(true)
+ .build();
+
+// Create a shared value
+auto shared = std::make_shared<std::string>("shared_value");
+
+// Reference it multiple times
+std::vector<std::shared_ptr<std::string>> data = {shared, shared, shared};
+
+// The shared value is serialized only once
+auto bytes = fory->serialize(data).value();
+auto decoded =
fory->deserialize<std::vector<std::shared_ptr<std::string>>>(bytes).value();
+
+// Verify reference identity is preserved
+assert(decoded[0].get() == decoded[1].get());
+assert(decoded[1].get() == decoded[2].get());
+```
+
+### 3. Schema Evolution
+
+Apache Fory™ supports schema evolution in **Compatible mode**, allowing
serialization and deserialization peers to have different type definitions.
+
+```cpp
+// Version 1
+struct PersonV1 {
+ std::string name;
+ int32_t age;
+ std::string address;
+ FORY_STRUCT(PersonV1, name, age, address);
+};
+
+// Version 2 - address removed, phone added
+struct PersonV2 {
+ std::string name;
+ int32_t age;
+ std::optional<std::string> phone;
+ FORY_STRUCT(PersonV2, name, age, phone);
+};
+
+auto fory1 = apache::fory::ForyBuilder()
+ .compatible(true)
+ .build();
+fory1->register_type<PersonV1>(1);
+
+auto fory2 = apache::fory::ForyBuilder()
+ .compatible(true)
+ .build();
+fory2->register_type<PersonV2>(1);
+
+PersonV1 v1{"Alice", 30, "123 Main St"};
+auto bytes = fory1->serialize(v1).value();
+
+// Deserialize with V2 - missing fields get default values
+auto v2 = fory2->deserialize<PersonV2>(bytes).value();
+assert(v2.name == "Alice");
+assert(v2.phone == std::nullopt);
+```
+
+### 4. Enum Support
+
+Apache Fory™ supports enum serialization with automatic ordinal mapping:
+
+```cpp
+// Continuous enum - works automatically
+enum class Color { Red, Green, Blue };
+
+// Non-continuous enum - needs FORY_ENUM
+enum class LegacyStatus { Active = 1, Inactive = 5, Pending = 10 };
+FORY_ENUM(LegacyStatus, Active, Inactive, Pending);
+
+struct Item {
+ std::string name;
+ Color color;
+ LegacyStatus status;
+ FORY_STRUCT(Item, name, color, status);
+};
+```
+
+### 5. Variant Support
+
+Apache Fory™ supports `std::variant` for type-safe union types:
+
+```cpp
+using Value = std::variant<std::monostate, bool, int32_t, std::string>;
+
+struct Config {
+ std::string key;
+ Value value;
+ FORY_STRUCT(Config, key, value);
+};
+
+Config config{"timeout", 30};
+auto bytes = fory->serialize(config).value();
+```
+
+### 6. Row-Based Serialization
+
+Apache Fory™ provides a high-performance **row format** for zero-copy
deserialization, enabling random access to fields directly from binary data.
+
+```cpp
+#include "fory/encoder/row_encoder.h"
+
+struct UserProfile {
+ int64_t id;
+ std::string username;
+ std::string email;
+ std::vector<int32_t> scores;
+ bool is_active;
+ FORY_FIELD_INFO(UserProfile, id, username, email, scores, is_active);
+};
+
+apache::fory::RowEncoder<UserProfile> encoder;
+
+UserProfile profile{12345, "alice", "[email protected]", {95, 87, 92}, true};
+
+// Encode to row format
+encoder.Encode(profile);
+auto& writer = encoder.GetWriter();
+
+// Access fields directly without full deserialization
+auto row = writer.ToRow();
+assert(row.GetInt64(0) == 12345); // id
+assert(row.GetString(1) == "alice"); // username
+assert(row.GetBool(4) == true); // is_active
+```
+
+## Cross-Language Serialization
+
+Apache Fory™ supports seamless data exchange across multiple languages:
+
+```cpp
+// Enable cross-language mode
+auto fory = apache::fory::ForyBuilder()
+ .xlang(true)
+ .compatible(true)
+ .build();
+
+// Register types with consistent IDs across languages
+fory->register_type<MyStruct>(1);
+```
+
+See
[xlang_type_mapping.md](https://fory.apache.org/docs/specification/xlang_type_mapping)
for type mapping across languages.
+
+## Thread Safety
+
+```cpp
+// Single-threaded (fastest performance)
+auto fory = apache::fory::ForyBuilder().build();
+
+// Thread-safe with internal Fory pool
+auto fory = apache::fory::ForyBuilder().build_thread_safe();
+```
+
+## Architecture
+
+The C++ implementation consists of these main components:
+
+```
+cpp/fory/
+├── serialization/ # Object graph serialization
+│ ├── fory.h # Main entry point (Fory, ThreadSafeFory)
+│ ├── config.h # Configuration options
+│ ├── serializer.h # Core serializer API
+│ ├── basic_serializer.h # Primitive type serializers
+│ ├── string_serializer.h # String type serializers
+│ ├── struct_serializer.h # Struct serialization with FORY_STRUCT
+│ ├── collection_serializer.h # vector, set serializers
+│ ├── map_serializer.h # map serializers
+│ ├── smart_ptr_serializers.h # optional, shared_ptr, unique_ptr
+│ ├── temporal_serializers.h # Duration, Timestamp, LocalDate
+│ ├── variant_serializer.h # std::variant support
+│ ├── type_resolver.h # Type resolution and registration
+│ └── context.h # Read/Write context
+├── encoder/ # Row format encoding
+│ ├── row_encoder.h # Row format encoder
+│ └── row_encode_trait.h # Encoding traits
+├── row/ # Row format data structures
+│ ├── row.h # Row, ArrayData, MapData
+│ ├── writer.h # RowWriter, ArrayWriter
+│ ├── schema.h # Schema definitions
+│ └── type.h # Type definitions
+├── meta/ # Compile-time reflection
+│ ├── field_info.h # Field metadata extraction
+│ └── type_traits.h # Type traits utilities
+└── util/ # Common utilities
+ ├── buffer.h # Binary buffer management
+ ├── error.h # Error handling
+ └── status.h # Status codes
+```
## Environment
-- Bazel version: 8.2.1
+- **C++ Standard**: C++17 or later
+- **Build System**: Bazel 8.2.1+ or CMake 3.16+
-## Build Apache Fory™ C++
+## Building
+
+### With Bazel
```bash
# Build all projects
-bazel build //:all
+bazel build //cpp/...
+
# Run all tests
-bazel test //:all
+bazel test $(bazel query //cpp/...)
+
# Run serialization tests
-bazel test //cpp/fory/serialization:all
+bazel test $(bazel query //cpp/fory/serialization/...)
```
-## Format Code
+### With CMake
```bash
+mkdir build && cd build
+cmake ..
+make -j$(nproc)
+```
+
+## Code Quality
+
+```bash
+# Format code
+clang-format -i <file>
+
+# Or use the CI script
bash ci/format.sh --cpp
```
+
+## Documentation
+
+- **[User Guide](https://fory.apache.org/docs/guide/cpp)** - Comprehensive
user documentation
+- **[Protocol
Specification](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec)**
- Serialization protocol details
+- **[Type
Mapping](https://fory.apache.org/docs/specification/xlang_type_mapping)** -
Cross-language type mappings
+- **[Source](https://github.com/apache/fory/tree/main/docs/guide/cpp)** -
Documentation source
+
+## Use Cases
+
+### Object Serialization
+
+- Complex data structures with nested objects and references
+- Cross-language communication in microservices
+- General-purpose serialization with full type safety
+- Schema evolution with compatible mode
+
+### Row-Based Serialization
+
+- High-throughput data processing
+- Analytics workloads requiring fast field access
+- Memory-constrained environments
+- Zero-copy scenarios
+
+## License
+
+Licensed under the Apache License, Version 2.0. See
[LICENSE](https://github.com/apache/fory/blob/main/LICENSE) for details.
+
+## Contributing
+
+We welcome contributions! Please see our [Contributing
Guide](https://github.com/apache/fory/blob/main/CONTRIBUTING.md) for details.
+
+## Support
+
+- **Issues**: [GitHub Issues](https://github.com/apache/fory/issues)
+- **Discussions**: [GitHub
Discussions](https://github.com/apache/fory/discussions)
+- **Slack**: [Apache Fory
Slack](https://join.slack.com/t/fory-project/shared_invite/zt-1u8soj4qc-ieYEu7ciHOqA2mo47llS8A)
+
+---
+
+**Apache Fory™** - Blazingly fast multi-language serialization framework.
diff --git a/cpp/fory/serialization/BUILD b/cpp/fory/serialization/BUILD
index fe51077c3..43f8825cb 100644
--- a/cpp/fory/serialization/BUILD
+++ b/cpp/fory/serialization/BUILD
@@ -21,6 +21,7 @@ cc_library(
"serializer_traits.h",
"skip.h",
"smart_ptr_serializers.h",
+ "string_serializer.h",
"struct_serializer.h",
"temporal_serializers.h",
"tuple_serializer.h",
diff --git a/cpp/fory/serialization/CMakeLists.txt
b/cpp/fory/serialization/CMakeLists.txt
index 509b2f6ee..b7306b65b 100644
--- a/cpp/fory/serialization/CMakeLists.txt
+++ b/cpp/fory/serialization/CMakeLists.txt
@@ -35,6 +35,7 @@ set(FORY_SERIALIZATION_HEADERS
serializer_traits.h
skip.h
smart_ptr_serializers.h
+ string_serializer.h
struct_serializer.h
temporal_serializers.h
tuple_serializer.h
diff --git a/cpp/fory/serialization/basic_serializer.h
b/cpp/fory/serialization/basic_serializer.h
index 68d8bb84f..4dd6198bc 100644
--- a/cpp/fory/serialization/basic_serializer.h
+++ b/cpp/fory/serialization/basic_serializer.h
@@ -23,11 +23,7 @@
#include "fory/serialization/serializer_traits.h"
#include "fory/type/type.h"
#include "fory/util/error.h"
-#include "fory/util/result.h"
-#include "fory/util/string_util.h"
#include <cstdint>
-#include <cstring>
-#include <string>
#include <type_traits>
namespace fory {
@@ -535,19 +531,12 @@ template <> struct Serializer<double> {
};
// ============================================================================
-// String Serializer
+// Character Type Serializers (C++ native only, not supported in xlang mode)
// ============================================================================
-/// std::string serializer using UTF-8 encoding per xlang spec
-template <> struct Serializer<std::string> {
- static constexpr TypeId type_id = TypeId::STRING;
-
- // String encoding types as per xlang spec
- enum class StringEncoding : uint8_t {
- LATIN1 = 0, // Latin1/ISO-8859-1
- UTF16 = 1, // UTF-16
- UTF8 = 2, // UTF-8
- };
+/// char serializer (C++ native only, type_id = 68)
+template <> struct Serializer<char> {
+ static constexpr TypeId type_id = TypeId::CHAR;
static inline void write_type_info(WriteContext &ctx) {
ctx.write_varuint32(static_cast<uint32_t>(type_id));
@@ -564,9 +553,8 @@ template <> struct Serializer<std::string> {
}
}
- static inline void write(const std::string &value, WriteContext &ctx,
- bool write_ref, bool write_type,
- bool has_generics = false) {
+ static inline void write(char value, WriteContext &ctx, bool write_ref,
+ bool write_type, bool has_generics = false) {
write_not_null_ref_flag(ctx, write_ref);
if (write_type) {
ctx.write_varuint32(static_cast<uint32_t>(type_id));
@@ -574,115 +562,192 @@ template <> struct Serializer<std::string> {
write_data(value, ctx);
}
- static inline void write_data(const std::string &value, WriteContext &ctx) {
- // Always use UTF-8 encoding for cross-language compatibility.
- // Per xlang spec: write size shifted left by 2 bits, with encoding
- // (UTF8) in the lower 2 bits. Use varuint36small encoding.
- uint64_t length = static_cast<uint64_t>(value.size());
- uint64_t size_with_encoding =
- (length << 2) | static_cast<uint64_t>(StringEncoding::UTF8);
- ctx.write_varuint36small(size_with_encoding);
-
- // Write string bytes
- if (!value.empty()) {
- ctx.write_bytes(value.data(), value.size());
- }
+ static inline void write_data(char value, WriteContext &ctx) {
+ ctx.write_int8(static_cast<int8_t>(value));
}
- static inline void write_data_generic(const std::string &value,
- WriteContext &ctx, bool has_generics) {
+ static inline void write_data_generic(char value, WriteContext &ctx,
+ bool has_generics) {
write_data(value, ctx);
}
- static inline std::string read(ReadContext &ctx, bool read_ref,
- bool read_type) {
+ static inline char read(ReadContext &ctx, bool read_ref, bool read_type) {
bool has_value = consume_ref_flag(ctx, read_ref);
if (ctx.has_error() || !has_value) {
- return std::string();
+ return '\0';
}
if (read_type) {
uint32_t type_id_read = ctx.read_varuint32(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
- return std::string();
+ return '\0';
}
if (type_id_read != static_cast<uint32_t>(type_id)) {
ctx.set_error(
Error::type_mismatch(type_id_read,
static_cast<uint32_t>(type_id)));
- return std::string();
+ return '\0';
}
}
return read_data(ctx);
}
- static inline std::string read_data(ReadContext &ctx) {
- // Read size with encoding using varuint36small
- uint64_t size_with_encoding = ctx.read_varuint36small(ctx.error());
- if (FORY_PREDICT_FALSE(ctx.has_error())) {
- return std::string();
- }
+ static inline char read_data(ReadContext &ctx) {
+ return static_cast<char>(ctx.read_int8(ctx.error()));
+ }
- // Extract size and encoding from lower 2 bits
- uint64_t length = size_with_encoding >> 2;
- StringEncoding encoding =
- static_cast<StringEncoding>(size_with_encoding & 0x3);
+ static inline char read_data_generic(ReadContext &ctx, bool has_generics) {
+ return read_data(ctx);
+ }
- if (length == 0) {
- return std::string();
+ static inline char read_with_type_info(ReadContext &ctx, bool read_ref,
+ const TypeInfo &type_info) {
+ return read(ctx, read_ref, false);
+ }
+};
+
+/// char16_t serializer (C++ native only, type_id = 69)
+template <> struct Serializer<char16_t> {
+ static constexpr TypeId type_id = TypeId::CHAR16;
+
+ static inline void write_type_info(WriteContext &ctx) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+
+ static inline void read_type_info(ReadContext &ctx) {
+ uint32_t actual = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ if (actual != static_cast<uint32_t>(type_id)) {
+ ctx.set_error(
+ Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
}
+ }
- // Validate length against buffer remaining size
- if (length > ctx.buffer().remaining_size()) {
- ctx.set_error(Error::invalid_data("String length exceeds buffer size"));
- return std::string();
+ static inline void write(char16_t value, WriteContext &ctx, bool write_ref,
+ bool write_type, bool has_generics = false) {
+ write_not_null_ref_flag(ctx, write_ref);
+ if (write_type) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
}
+ write_data(value, ctx);
+ }
- // Handle different encodings
- switch (encoding) {
- case StringEncoding::LATIN1: {
- std::vector<uint8_t> bytes(length);
- ctx.read_bytes(bytes.data(), length, ctx.error());
- if (FORY_PREDICT_FALSE(ctx.has_error())) {
- return std::string();
- }
- return latin1ToUtf8(bytes.data(), length);
+ static inline void write_data(char16_t value, WriteContext &ctx) {
+ ctx.write_bytes(&value, sizeof(char16_t));
+ }
+
+ static inline void write_data_generic(char16_t value, WriteContext &ctx,
+ bool has_generics) {
+ write_data(value, ctx);
+ }
+
+ static inline char16_t read(ReadContext &ctx, bool read_ref, bool read_type)
{
+ bool has_value = consume_ref_flag(ctx, read_ref);
+ if (ctx.has_error() || !has_value) {
+ return u'\0';
}
- case StringEncoding::UTF16: {
- if (length % 2 != 0) {
- ctx.set_error(Error::invalid_data("UTF-16 length must be even"));
- return std::string();
- }
- std::vector<uint16_t> utf16_chars(length / 2);
- ctx.read_bytes(reinterpret_cast<uint8_t *>(utf16_chars.data()), length,
- ctx.error());
+ if (read_type) {
+ uint32_t type_id_read = ctx.read_varuint32(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
- return std::string();
+ return u'\0';
}
- return utf16ToUtf8(utf16_chars.data(), utf16_chars.size());
- }
- case StringEncoding::UTF8: {
- // UTF-8: read bytes directly
- std::string result(length, '\0');
- ctx.read_bytes(&result[0], length, ctx.error());
- if (FORY_PREDICT_FALSE(ctx.has_error())) {
- return std::string();
+ if (type_id_read != static_cast<uint32_t>(type_id)) {
+ ctx.set_error(
+ Error::type_mismatch(type_id_read,
static_cast<uint32_t>(type_id)));
+ return u'\0';
}
- return result;
}
- default:
+ return read_data(ctx);
+ }
+
+ static inline char16_t read_data(ReadContext &ctx) {
+ char16_t value;
+ ctx.read_bytes(reinterpret_cast<uint8_t *>(&value), sizeof(char16_t),
+ ctx.error());
+ return value;
+ }
+
+ static inline char16_t read_data_generic(ReadContext &ctx,
+ bool has_generics) {
+ return read_data(ctx);
+ }
+
+ static inline char16_t read_with_type_info(ReadContext &ctx, bool read_ref,
+ const TypeInfo &type_info) {
+ return read(ctx, read_ref, false);
+ }
+};
+
+/// char32_t serializer (C++ native only, type_id = 70)
+template <> struct Serializer<char32_t> {
+ static constexpr TypeId type_id = TypeId::CHAR32;
+
+ static inline void write_type_info(WriteContext &ctx) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+
+ static inline void read_type_info(ReadContext &ctx) {
+ uint32_t actual = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ if (actual != static_cast<uint32_t>(type_id)) {
ctx.set_error(
- Error::encoding_error("Unknown string encoding: " +
- std::to_string(static_cast<int>(encoding))));
- return std::string();
+ Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+ }
+ }
+
+ static inline void write(char32_t value, WriteContext &ctx, bool write_ref,
+ bool write_type, bool has_generics = false) {
+ write_not_null_ref_flag(ctx, write_ref);
+ if (write_type) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
}
+ write_data(value, ctx);
+ }
+
+ static inline void write_data(char32_t value, WriteContext &ctx) {
+ ctx.write_bytes(&value, sizeof(char32_t));
+ }
+
+ static inline void write_data_generic(char32_t value, WriteContext &ctx,
+ bool has_generics) {
+ write_data(value, ctx);
+ }
+
+ static inline char32_t read(ReadContext &ctx, bool read_ref, bool read_type)
{
+ bool has_value = consume_ref_flag(ctx, read_ref);
+ if (ctx.has_error() || !has_value) {
+ return U'\0';
+ }
+ if (read_type) {
+ uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return U'\0';
+ }
+ if (type_id_read != static_cast<uint32_t>(type_id)) {
+ ctx.set_error(
+ Error::type_mismatch(type_id_read,
static_cast<uint32_t>(type_id)));
+ return U'\0';
+ }
+ }
+ return read_data(ctx);
+ }
+
+ static inline char32_t read_data(ReadContext &ctx) {
+ char32_t value;
+ ctx.read_bytes(reinterpret_cast<uint8_t *>(&value), sizeof(char32_t),
+ ctx.error());
+ return value;
}
- static inline std::string read_data_generic(ReadContext &ctx,
- bool has_generics) {
+ static inline char32_t read_data_generic(ReadContext &ctx,
+ bool has_generics) {
return read_data(ctx);
}
- static inline std::string read_with_type_info(ReadContext &ctx, bool
read_ref,
- const TypeInfo &type_info) {
+ static inline char32_t read_with_type_info(ReadContext &ctx, bool read_ref,
+ const TypeInfo &type_info) {
return read(ctx, read_ref, false);
}
};
diff --git a/cpp/fory/serialization/serialization_test.cc
b/cpp/fory/serialization/serialization_test.cc
index bf9d7e17d..c2dd16007 100644
--- a/cpp/fory/serialization/serialization_test.cc
+++ b/cpp/fory/serialization/serialization_test.cc
@@ -182,6 +182,36 @@ TEST(SerializationTest, StringRoundtrip) {
test_roundtrip(std::string("UTF-8: 你好世界"));
}
+// ============================================================================
+// Character Type Tests (C++ native only)
+// ============================================================================
+
+TEST(SerializationTest, CharRoundtrip) {
+ test_roundtrip<char>('A');
+ test_roundtrip<char>('z');
+ test_roundtrip<char>('0');
+ test_roundtrip<char>('\0');
+ test_roundtrip<char>('\n');
+ test_roundtrip<char>(static_cast<char>(127));
+ test_roundtrip<char>(static_cast<char>(-128));
+}
+
+TEST(SerializationTest, Char16Roundtrip) {
+ test_roundtrip<char16_t>(u'A');
+ test_roundtrip<char16_t>(u'中');
+ test_roundtrip<char16_t>(u'\0');
+ test_roundtrip<char16_t>(static_cast<char16_t>(0xFFFF));
+ test_roundtrip<char16_t>(static_cast<char16_t>(0x4E2D)); // 中
+}
+
+TEST(SerializationTest, Char32Roundtrip) {
+ test_roundtrip<char32_t>(U'A');
+ test_roundtrip<char32_t>(U'中');
+ test_roundtrip<char32_t>(U'\0');
+ test_roundtrip<char32_t>(static_cast<char32_t>(0x10FFFF)); // Max Unicode
+ test_roundtrip<char32_t>(static_cast<char32_t>(0x1F600)); // Emoji 😀
+}
+
// ============================================================================
// Enum Tests
// ============================================================================
diff --git a/cpp/fory/serialization/serializer.h
b/cpp/fory/serialization/serializer.h
index 1cac3b37b..ea717c8da 100644
--- a/cpp/fory/serialization/serializer.h
+++ b/cpp/fory/serialization/serializer.h
@@ -242,4 +242,5 @@ template <typename T, typename Enable> struct Serializer {
// Include all specialized serializers
#include "fory/serialization/basic_serializer.h"
#include "fory/serialization/enum_serializer.h"
+#include "fory/serialization/string_serializer.h"
#include "fory/serialization/unsigned_serializer.h"
diff --git a/cpp/fory/serialization/string_serializer.h
b/cpp/fory/serialization/string_serializer.h
new file mode 100644
index 000000000..f1bfb24c3
--- /dev/null
+++ b/cpp/fory/serialization/string_serializer.h
@@ -0,0 +1,421 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "fory/serialization/context.h"
+#include "fory/serialization/serializer_traits.h"
+#include "fory/type/type.h"
+#include "fory/util/error.h"
+#include "fory/util/string_util.h"
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace fory {
+namespace serialization {
+
+// String encoding types as per xlang spec
+enum class StringEncoding : uint8_t {
+ LATIN1 = 0, // Latin1/ISO-8859-1
+ UTF16 = 1, // UTF-16
+ UTF8 = 2, // UTF-8
+};
+
+// ============================================================================
+// Internal helper functions for string serialization
+// ============================================================================
+
+namespace detail {
+
+/// Write string data with UTF-8 encoding
+inline void write_string_data(const char *data, size_t size,
+ WriteContext &ctx) {
+ // Always use UTF-8 encoding for cross-language compatibility.
+ // Per xlang spec: write size shifted left by 2 bits, with encoding
+ // (UTF8) in the lower 2 bits. Use varuint36small encoding.
+ uint64_t length = static_cast<uint64_t>(size);
+ uint64_t size_with_encoding =
+ (length << 2) | static_cast<uint64_t>(StringEncoding::UTF8);
+ ctx.write_varuint36small(size_with_encoding);
+
+ // Write string bytes
+ if (size > 0) {
+ ctx.write_bytes(data, size);
+ }
+}
+
+/// Write UTF-16 string data, converting to UTF-8 or using native encoding
+inline void write_u16string_data(const char16_t *data, size_t size,
+ WriteContext &ctx) {
+ if (size == 0) {
+ // Empty string: write zero length with UTF8 encoding
+ ctx.write_varuint36small(static_cast<uint64_t>(StringEncoding::UTF8));
+ return;
+ }
+
+ const uint16_t *u16_data = reinterpret_cast<const uint16_t *>(data);
+
+ // Check if string can be encoded as Latin1 (more compact)
+ if (isLatin1(u16_data, size)) {
+ // Encode as Latin1 for compactness
+ uint64_t size_with_encoding = (static_cast<uint64_t>(size) << 2) |
+
static_cast<uint64_t>(StringEncoding::LATIN1);
+ ctx.write_varuint36small(size_with_encoding);
+
+ // Write each char16_t as a single byte
+ for (size_t i = 0; i < size; ++i) {
+ ctx.write_uint8(static_cast<uint8_t>(data[i]));
+ }
+ } else {
+ // Convert to UTF-8
+ std::string utf8 = utf16ToUtf8(u16_data, size);
+ uint64_t size_with_encoding = (static_cast<uint64_t>(utf8.size()) << 2) |
+ static_cast<uint64_t>(StringEncoding::UTF8);
+ ctx.write_varuint36small(size_with_encoding);
+ if (!utf8.empty()) {
+ ctx.write_bytes(utf8.data(), utf8.size());
+ }
+ }
+}
+
+/// Read string data and return as std::string
+inline std::string read_string_data(ReadContext &ctx) {
+ // Read size with encoding using varuint36small
+ uint64_t size_with_encoding = ctx.read_varuint36small(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::string();
+ }
+
+ // Extract size and encoding from lower 2 bits
+ uint64_t length = size_with_encoding >> 2;
+ StringEncoding encoding =
+ static_cast<StringEncoding>(size_with_encoding & 0x3);
+
+ if (length == 0) {
+ return std::string();
+ }
+
+ // Validate length against buffer remaining size
+ if (length > ctx.buffer().remaining_size()) {
+ ctx.set_error(Error::invalid_data("String length exceeds buffer size"));
+ return std::string();
+ }
+
+ // Handle different encodings
+ switch (encoding) {
+ case StringEncoding::LATIN1: {
+ std::vector<uint8_t> bytes(length);
+ ctx.read_bytes(bytes.data(), length, ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::string();
+ }
+ return latin1ToUtf8(bytes.data(), length);
+ }
+ case StringEncoding::UTF16: {
+ if (length % 2 != 0) {
+ ctx.set_error(Error::invalid_data("UTF-16 length must be even"));
+ return std::string();
+ }
+ std::vector<uint16_t> utf16_chars(length / 2);
+ ctx.read_bytes(reinterpret_cast<uint8_t *>(utf16_chars.data()), length,
+ ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::string();
+ }
+ return utf16ToUtf8(utf16_chars.data(), utf16_chars.size());
+ }
+ case StringEncoding::UTF8: {
+ // UTF-8: read bytes directly
+ std::string result(length, '\0');
+ ctx.read_bytes(&result[0], length, ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::string();
+ }
+ return result;
+ }
+ default:
+ ctx.set_error(
+ Error::encoding_error("Unknown string encoding: " +
+ std::to_string(static_cast<int>(encoding))));
+ return std::string();
+ }
+}
+
+/// Read string data and return as std::u16string
+inline std::u16string read_u16string_data(ReadContext &ctx) {
+ // Read size with encoding using varuint36small
+ uint64_t size_with_encoding = ctx.read_varuint36small(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::u16string();
+ }
+
+ // Extract size and encoding from lower 2 bits
+ uint64_t length = size_with_encoding >> 2;
+ StringEncoding encoding =
+ static_cast<StringEncoding>(size_with_encoding & 0x3);
+
+ if (length == 0) {
+ return std::u16string();
+ }
+
+ // Validate length against buffer remaining size
+ if (length > ctx.buffer().remaining_size()) {
+ ctx.set_error(Error::invalid_data("String length exceeds buffer size"));
+ return std::u16string();
+ }
+
+ // Handle different encodings
+ switch (encoding) {
+ case StringEncoding::LATIN1: {
+ // Latin1 bytes map directly to char16_t (codepoints 0-255)
+ std::u16string result(length, u'\0');
+ for (size_t i = 0; i < length; ++i) {
+ result[i] = static_cast<char16_t>(ctx.read_uint8(ctx.error()));
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::u16string();
+ }
+ }
+ return result;
+ }
+ case StringEncoding::UTF16: {
+ if (length % 2 != 0) {
+ ctx.set_error(Error::invalid_data("UTF-16 length must be even"));
+ return std::u16string();
+ }
+ std::u16string result(length / 2, u'\0');
+ ctx.read_bytes(reinterpret_cast<uint8_t *>(&result[0]), length,
+ ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::u16string();
+ }
+ return result;
+ }
+ case StringEncoding::UTF8: {
+ // Read UTF-8 bytes and convert to UTF-16
+ std::string utf8(length, '\0');
+ ctx.read_bytes(&utf8[0], length, ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::u16string();
+ }
+ return utf8ToUtf16(utf8, true /* little endian */);
+ }
+ default:
+ ctx.set_error(
+ Error::encoding_error("Unknown string encoding: " +
+ std::to_string(static_cast<int>(encoding))));
+ return std::u16string();
+ }
+}
+
+} // namespace detail
+
+// ============================================================================
+// std::string Serializer
+// ============================================================================
+
+/// std::string serializer using UTF-8 encoding per xlang spec
+template <> struct Serializer<std::string> {
+ static constexpr TypeId type_id = TypeId::STRING;
+
+ static inline void write_type_info(WriteContext &ctx) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+
+ static inline void read_type_info(ReadContext &ctx) {
+ uint32_t actual = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ if (actual != static_cast<uint32_t>(type_id)) {
+ ctx.set_error(
+ Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+ }
+ }
+
+ static inline void write(const std::string &value, WriteContext &ctx,
+ bool write_ref, bool write_type,
+ bool has_generics = false) {
+ write_not_null_ref_flag(ctx, write_ref);
+ if (write_type) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+ write_data(value, ctx);
+ }
+
+ static inline void write_data(const std::string &value, WriteContext &ctx) {
+ detail::write_string_data(value.data(), value.size(), ctx);
+ }
+
+ static inline void write_data_generic(const std::string &value,
+ WriteContext &ctx, bool has_generics) {
+ write_data(value, ctx);
+ }
+
+ static inline std::string read(ReadContext &ctx, bool read_ref,
+ bool read_type) {
+ bool has_value = consume_ref_flag(ctx, read_ref);
+ if (ctx.has_error() || !has_value) {
+ return std::string();
+ }
+ if (read_type) {
+ uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::string();
+ }
+ if (type_id_read != static_cast<uint32_t>(type_id)) {
+ ctx.set_error(
+ Error::type_mismatch(type_id_read,
static_cast<uint32_t>(type_id)));
+ return std::string();
+ }
+ }
+ return read_data(ctx);
+ }
+
+ static inline std::string read_data(ReadContext &ctx) {
+ return detail::read_string_data(ctx);
+ }
+
+ static inline std::string read_data_generic(ReadContext &ctx,
+ bool has_generics) {
+ return read_data(ctx);
+ }
+
+ static inline std::string read_with_type_info(ReadContext &ctx, bool
read_ref,
+ const TypeInfo &type_info) {
+ return read(ctx, read_ref, false);
+ }
+};
+
+// ============================================================================
+// std::string_view Serializer (write-only, reads as std::string)
+// ============================================================================
+
+/// std::string_view serializer - write-only for zero-copy serialization
+/// Note: Deserialization returns std::string since string_view requires
+/// stable backing storage
+template <> struct Serializer<std::string_view> {
+ static constexpr TypeId type_id = TypeId::STRING;
+
+ static inline void write_type_info(WriteContext &ctx) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+
+ static inline void write(std::string_view value, WriteContext &ctx,
+ bool write_ref, bool write_type,
+ bool has_generics = false) {
+ write_not_null_ref_flag(ctx, write_ref);
+ if (write_type) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+ write_data(value, ctx);
+ }
+
+ static inline void write_data(std::string_view value, WriteContext &ctx) {
+ detail::write_string_data(value.data(), value.size(), ctx);
+ }
+
+ static inline void write_data_generic(std::string_view value,
+ WriteContext &ctx, bool has_generics) {
+ write_data(value, ctx);
+ }
+};
+
+// ============================================================================
+// std::u16string Serializer
+// ============================================================================
+
+/// std::u16string serializer with UTF-16 support
+template <> struct Serializer<std::u16string> {
+ static constexpr TypeId type_id = TypeId::STRING;
+
+ static inline void write_type_info(WriteContext &ctx) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+
+ static inline void read_type_info(ReadContext &ctx) {
+ uint32_t actual = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ if (actual != static_cast<uint32_t>(type_id)) {
+ ctx.set_error(
+ Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+ }
+ }
+
+ static inline void write(const std::u16string &value, WriteContext &ctx,
+ bool write_ref, bool write_type,
+ bool has_generics = false) {
+ write_not_null_ref_flag(ctx, write_ref);
+ if (write_type) {
+ ctx.write_varuint32(static_cast<uint32_t>(type_id));
+ }
+ write_data(value, ctx);
+ }
+
+ static inline void write_data(const std::u16string &value,
+ WriteContext &ctx) {
+ detail::write_u16string_data(value.data(), value.size(), ctx);
+ }
+
+ static inline void write_data_generic(const std::u16string &value,
+ WriteContext &ctx, bool has_generics) {
+ write_data(value, ctx);
+ }
+
+ static inline std::u16string read(ReadContext &ctx, bool read_ref,
+ bool read_type) {
+ bool has_value = consume_ref_flag(ctx, read_ref);
+ if (ctx.has_error() || !has_value) {
+ return std::u16string();
+ }
+ if (read_type) {
+ uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::u16string();
+ }
+ if (type_id_read != static_cast<uint32_t>(type_id)) {
+ ctx.set_error(
+ Error::type_mismatch(type_id_read,
static_cast<uint32_t>(type_id)));
+ return std::u16string();
+ }
+ }
+ return read_data(ctx);
+ }
+
+ static inline std::u16string read_data(ReadContext &ctx) {
+ return detail::read_u16string_data(ctx);
+ }
+
+ static inline std::u16string read_data_generic(ReadContext &ctx,
+ bool has_generics) {
+ return read_data(ctx);
+ }
+
+ static inline std::u16string read_with_type_info(ReadContext &ctx,
+ bool read_ref,
+ const TypeInfo &type_info) {
+ return read(ctx, read_ref, false);
+ }
+};
+
+} // namespace serialization
+} // namespace fory
diff --git a/cpp/fory/type/type.h b/cpp/fory/type/type.h
index 9501e4fc4..75c622f84 100644
--- a/cpp/fory/type/type.h
+++ b/cpp/fory/type/type.h
@@ -116,6 +116,12 @@ enum class TypeId : int32_t {
U32 = 66,
// a 64-bit unsigned integer.
U64 = 67,
+ // 8-bits character.
+ CHAR = 68,
+ // 16-bits character
+ CHAR16 = 69,
+ // 32-bits character
+ CHAR32 = 70,
// Unsigned integer array types (native mode only)
// one-dimensional uint16 array.
U16_ARRAY = 73,
diff --git a/docs/guide/cpp/basic-serialization.md
b/docs/guide/cpp/basic-serialization.md
new file mode 100644
index 000000000..83182495e
--- /dev/null
+++ b/docs/guide/cpp/basic-serialization.md
@@ -0,0 +1,238 @@
+---
+title: Basic Serialization
+sidebar_position: 2
+id: cpp_basic_serialization
+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.
+---
+
+This page covers basic object graph serialization and the core serialization
APIs.
+
+## Object Graph Serialization
+
+Apache Fory™ provides automatic serialization of complex object graphs,
preserving the structure and relationships between objects. The `FORY_STRUCT`
macro generates efficient serialization code at compile time, eliminating
runtime overhead.
+
+**Key capabilities:**
+
+- Nested struct serialization with arbitrary depth
+- Collection types (vector, set, map)
+- Optional fields with `std::optional<T>`
+- Smart pointers (`std::shared_ptr`, `std::unique_ptr`)
+- Automatic handling of primitive types and strings
+- Efficient binary encoding with variable-length integers
+
+```cpp
+#include "fory/serialization/fory.h"
+#include <vector>
+#include <map>
+
+using namespace fory::serialization;
+
+// Define structs
+struct Address {
+ std::string street;
+ std::string city;
+ std::string country;
+
+ bool operator==(const Address &other) const {
+ return street == other.street && city == other.city &&
+ country == other.country;
+ }
+};
+FORY_STRUCT(Address, street, city, country);
+
+struct Person {
+ std::string name;
+ int32_t age;
+ Address address;
+ std::vector<std::string> hobbies;
+ std::map<std::string, std::string> metadata;
+
+ bool operator==(const Person &other) const {
+ return name == other.name && age == other.age &&
+ address == other.address && hobbies == other.hobbies &&
+ metadata == other.metadata;
+ }
+};
+FORY_STRUCT(Person, name, age, address, hobbies, metadata);
+
+int main() {
+ auto fory = Fory::builder().xlang(true).build();
+ fory.register_struct<Address>(100);
+ fory.register_struct<Person>(200);
+
+ Person person{
+ "John Doe",
+ 30,
+ {"123 Main St", "New York", "USA"},
+ {"reading", "coding"},
+ {{"role", "developer"}}
+ };
+
+ auto result = fory.serialize(person);
+ auto decoded = fory.deserialize<Person>(result.value());
+ assert(person == decoded.value());
+}
+```
+
+## Serialization APIs
+
+### Serialize to New Vector
+
+```cpp
+auto fory = Fory::builder().xlang(true).build();
+fory.register_struct<MyStruct>(1);
+
+MyStruct obj{/* ... */};
+
+// Serialize - returns Result<std::vector<uint8_t>, Error>
+auto result = fory.serialize(obj);
+if (result.ok()) {
+ std::vector<uint8_t> bytes = std::move(result).value();
+ // Use bytes...
+} else {
+ // Handle error
+ std::cerr << result.error().to_string() << std::endl;
+}
+```
+
+### Serialize to Existing Buffer
+
+```cpp
+// Serialize to existing Buffer (fastest path)
+Buffer buffer;
+auto result = fory.serialize_to(obj, buffer);
+if (result.ok()) {
+ size_t bytes_written = result.value();
+ // buffer now contains serialized data
+}
+
+// Serialize to existing vector (zero-copy)
+std::vector<uint8_t> output;
+auto result = fory.serialize_to(obj, output);
+if (result.ok()) {
+ size_t bytes_written = result.value();
+ // output now contains serialized data
+}
+```
+
+### Deserialize from Byte Array
+
+```cpp
+// Deserialize from raw pointer
+auto result = fory.deserialize<MyStruct>(data_ptr, data_size);
+if (result.ok()) {
+ MyStruct obj = std::move(result).value();
+}
+
+// Deserialize from vector
+std::vector<uint8_t> data = /* ... */;
+auto result = fory.deserialize<MyStruct>(data);
+
+// Deserialize from Buffer (updates reader_index)
+Buffer buffer(data);
+auto result = fory.deserialize<MyStruct>(buffer);
+```
+
+## Error Handling
+
+Fory uses a `Result<T, Error>` type for error handling:
+
+```cpp
+auto result = fory.serialize(obj);
+
+// Check if operation succeeded
+if (result.ok()) {
+ auto value = std::move(result).value();
+ // Use value...
+} else {
+ Error error = result.error();
+ std::cerr << "Error: " << error.to_string() << std::endl;
+}
+
+// Or use FORY_TRY macro for early return
+FORY_TRY(bytes, fory.serialize(obj));
+// Use bytes directly...
+```
+
+Common error types:
+
+- `Error::type_mismatch` - Type ID mismatch during deserialization
+- `Error::invalid_data` - Invalid or corrupted data
+- `Error::buffer_out_of_bound` - Buffer overflow/underflow
+- `Error::type_error` - Type registration error
+
+## The FORY_STRUCT Macro
+
+The `FORY_STRUCT` macro registers a struct for serialization:
+
+```cpp
+struct MyStruct {
+ int32_t x;
+ std::string y;
+ std::vector<int32_t> z;
+};
+
+// Must be in the same namespace as the struct
+FORY_STRUCT(MyStruct, x, y, z);
+```
+
+The macro:
+
+1. Generates compile-time field metadata
+2. Enables ADL (Argument-Dependent Lookup) for serialization
+3. Creates efficient serialization code via template specialization
+
+**Requirements:**
+
+- Must be placed in the same namespace as the struct (for ADL)
+- All listed fields must be serializable types
+- Field order in the macro determines serialization order
+
+## Nested Structs
+
+Nested structs are fully supported:
+
+```cpp
+struct Inner {
+ int32_t value;
+};
+FORY_STRUCT(Inner, value);
+
+struct Outer {
+ Inner inner;
+ std::string label;
+};
+FORY_STRUCT(Outer, inner, label);
+
+// Both must be registered
+fory.register_struct<Inner>(1);
+fory.register_struct<Outer>(2);
+```
+
+## Performance Tips
+
+- **Buffer Reuse**: Use `serialize_to(obj, buffer)` with pre-allocated buffers
+- **Pre-registration**: Register all types before serialization starts
+- **Single-Threaded**: Use `build()` instead of `build_thread_safe()` when
possible
+- **Disable Tracking**: Use `track_ref(false)` when references aren't needed
+- **Compact Encoding**: Variable-length encoding for space efficiency
+
+## Related Topics
+
+- [Configuration](configuration.md) - Builder options
+- [Type Registration](type-registration.md) - Registering types
+- [Supported Types](supported-types.md) - All supported types
diff --git a/docs/guide/cpp/configuration.md b/docs/guide/cpp/configuration.md
new file mode 100644
index 000000000..5febe5559
--- /dev/null
+++ b/docs/guide/cpp/configuration.md
@@ -0,0 +1,189 @@
+---
+title: Configuration
+sidebar_position: 1
+id: cpp_configuration
+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.
+---
+
+This page covers Fory configuration options and serialization modes.
+
+## Serialization Modes
+
+Apache Fory™ supports two serialization modes:
+
+### SchemaConsistent Mode (Default)
+
+Type declarations must match exactly between peers:
+
+```cpp
+auto fory = Fory::builder().build(); // SchemaConsistent by default
+```
+
+### Compatible Mode
+
+Allows independent schema evolution:
+
+```cpp
+auto fory = Fory::builder().compatible(true).build();
+```
+
+## Builder Pattern
+
+Use `ForyBuilder` to construct Fory instances with custom configuration:
+
+```cpp
+#include "fory/serialization/fory.h"
+
+using namespace fory::serialization;
+
+// Default configuration
+auto fory = Fory::builder().build();
+
+// Compatible mode for schema evolution
+auto fory = Fory::builder()
+ .compatible(true)
+ .build();
+
+// Cross-language mode
+auto fory = Fory::builder()
+ .xlang(true)
+ .build();
+
+// Full configuration
+auto fory = Fory::builder()
+ .compatible(true)
+ .xlang(true)
+ .track_ref(true)
+ .max_dyn_depth(10)
+ .check_struct_version(true)
+ .build();
+```
+
+## Configuration Options
+
+### xlang(bool)
+
+Enable/disable cross-language (xlang) serialization mode.
+
+```cpp
+auto fory = Fory::builder()
+ .xlang(true) // Enable cross-language compatibility
+ .build();
+```
+
+When enabled, includes metadata for cross-language compatibility with Java,
Python, Go, Rust, and JavaScript.
+
+**Default:** `true`
+
+### compatible(bool)
+
+Enable/disable compatible mode for schema evolution.
+
+```cpp
+auto fory = Fory::builder()
+ .compatible(true) // Enable schema evolution
+ .build();
+```
+
+When enabled, supports reading data serialized with different schema versions.
+
+**Default:** `false`
+
+### track_ref(bool)
+
+Enable/disable reference tracking for shared and circular references.
+
+```cpp
+auto fory = Fory::builder()
+ .track_ref(true) // Enable reference tracking
+ .build();
+```
+
+When enabled, avoids duplicating shared objects and handles cycles.
+
+**Default:** `true`
+
+### max_dyn_depth(uint32_t)
+
+Set maximum allowed nesting depth for dynamically-typed objects.
+
+```cpp
+auto fory = Fory::builder()
+ .max_dyn_depth(10) // Allow up to 10 levels
+ .build();
+```
+
+This limits the maximum depth for nested polymorphic object serialization
(e.g., `shared_ptr<Base>`, `unique_ptr<Base>`). This prevents stack overflow
from deeply nested structures in dynamic serialization scenarios.
+
+**Default:** `5`
+
+**When to adjust:**
+
+- **Increase**: For legitimate deeply nested data structures
+- **Decrease**: For stricter security requirements or shallow data structures
+
+### check_struct_version(bool)
+
+Enable/disable struct version checking.
+
+```cpp
+auto fory = Fory::builder()
+ .check_struct_version(true) // Enable version checking
+ .build();
+```
+
+When enabled, validates type hashes to detect schema mismatches.
+
+**Default:** `false`
+
+## Thread-Safe vs Single-Threaded
+
+### Single-Threaded (Fastest)
+
+```cpp
+auto fory = Fory::builder()
+ .xlang(true)
+ .build(); // Returns Fory
+```
+
+Single-threaded `Fory` is the fastest option, but NOT thread-safe. Use one
instance per thread.
+
+### Thread-Safe
+
+```cpp
+auto fory = Fory::builder()
+ .xlang(true)
+ .build_thread_safe(); // Returns ThreadSafeFory
+```
+
+`ThreadSafeFory` uses a pool of Fory instances to provide thread-safe
serialization. Slightly slower due to pool overhead, but safe to use from
multiple threads concurrently.
+
+## Configuration Summary
+
+| Option | Description |
Default |
+| ---------------------------- | --------------------------------------- |
------- |
+| `xlang(bool)` | Enable cross-language mode |
`true` |
+| `compatible(bool)` | Enable schema evolution |
`false` |
+| `track_ref(bool)` | Enable reference tracking |
`true` |
+| `max_dyn_depth(uint32_t)` | Maximum nesting depth for dynamic types | `5`
|
+| `check_struct_version(bool)` | Enable struct version checking |
`false` |
+
+## Related Topics
+
+- [Basic Serialization](basic-serialization.md) - Using configured Fory
+- [Cross-Language](cross-language.md) - XLANG mode details
+- [Type Registration](type-registration.md) - Registering types
diff --git a/docs/guide/cpp/cross-language.md b/docs/guide/cpp/cross-language.md
new file mode 100644
index 000000000..3bf81cdae
--- /dev/null
+++ b/docs/guide/cpp/cross-language.md
@@ -0,0 +1,271 @@
+---
+title: Cross-Language Serialization
+sidebar_position: 6
+id: cpp_cross_language
+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.
+---
+
+This page explains how to use Fory for cross-language serialization between
C++ and other languages.
+
+## Overview
+
+Apache Fory™ enables seamless data exchange between C++, Java, Python, Go,
Rust, and JavaScript. The xlang (cross-language) mode ensures binary
compatibility across all supported languages.
+
+## Enabling Cross-Language Mode
+
+```cpp
+#include "fory/serialization/fory.h"
+
+using namespace fory::serialization;
+
+auto fory = Fory::builder()
+ .xlang(true) // Enable cross-language mode
+ .build();
+```
+
+## Cross-Language Example
+
+### C++ Producer
+
+```cpp
+#include "fory/serialization/fory.h"
+#include <fstream>
+
+using namespace fory::serialization;
+
+struct Message {
+ std::string topic;
+ int64_t timestamp;
+ std::map<std::string, std::string> headers;
+ std::vector<uint8_t> payload;
+
+ bool operator==(const Message &other) const {
+ return topic == other.topic && timestamp == other.timestamp &&
+ headers == other.headers && payload == other.payload;
+ }
+};
+FORY_STRUCT(Message, topic, timestamp, headers, payload);
+
+int main() {
+ auto fory = Fory::builder().xlang(true).build();
+ fory.register_struct<Message>(100);
+
+ Message msg{
+ "events.user",
+ 1699999999000,
+ {{"content-type", "application/json"}},
+ {'h', 'e', 'l', 'l', 'o'}
+ };
+
+ auto result = fory.serialize(msg);
+ if (result.ok()) {
+ auto bytes = std::move(result).value();
+ // Write to file, send over network, etc.
+ std::ofstream file("message.bin", std::ios::binary);
+ file.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
+ }
+ return 0;
+}
+```
+
+### Java Consumer
+
+```java
+import org.apache.fory.Fory;
+import org.apache.fory.config.Language;
+
+public class Message {
+ public String topic;
+ public long timestamp;
+ public Map<String, String> headers;
+ public byte[] payload;
+}
+
+public class Consumer {
+ public static void main(String[] args) throws Exception {
+ Fory fory = Fory.builder()
+ .withLanguage(Language.XLANG)
+ .build();
+ fory.register(Message.class, 100); // Same ID as C++
+
+ byte[] bytes = Files.readAllBytes(Path.of("message.bin"));
+ Message msg = (Message) fory.deserialize(bytes);
+
+ System.out.println("Topic: " + msg.topic);
+ System.out.println("Timestamp: " + msg.timestamp);
+ }
+}
+```
+
+### Python Consumer
+
+```python
+import pyfory
+
+class Message:
+ topic: str
+ timestamp: int
+ headers: dict[str, str]
+ payload: bytes
+
+fory = pyfory.Fory()
+fory.register(Message, type_id=100) # Same ID as C++
+
+with open("message.bin", "rb") as f:
+ data = f.read()
+
+msg = fory.deserialize(data)
+print(f"Topic: {msg.topic}")
+print(f"Timestamp: {msg.timestamp}")
+```
+
+## Type Mapping
+
+### Primitive Types
+
+| C++ Type | Java Type | Python Type | Go Type | Rust Type |
+| --------- | --------- | ----------- | --------- | --------- |
+| `bool` | `boolean` | `bool` | `bool` | `bool` |
+| `int8_t` | `byte` | `int` | `int8` | `i8` |
+| `int16_t` | `short` | `int` | `int16` | `i16` |
+| `int32_t` | `int` | `int` | `int32` | `i32` |
+| `int64_t` | `long` | `int` | `int64` | `i64` |
+| `float` | `float` | `float` | `float32` | `f32` |
+| `double` | `double` | `float` | `float64` | `f64` |
+
+### String Types
+
+| C++ Type | Java Type | Python Type | Go Type | Rust Type |
+| ------------- | --------- | ----------- | -------- | --------- |
+| `std::string` | `String` | `str` | `string` | `String` |
+
+### Collection Types
+
+| C++ Type | Java Type | Python Type | Go Type |
+| ---------------- | ---------- | ----------- | ---------------- |
+| `std::vector<T>` | `List<T>` | `list` | `[]T` |
+| `std::set<T>` | `Set<T>` | `set` | `map[T]struct{}` |
+| `std::map<K,V>` | `Map<K,V>` | `dict` | `map[K]V` |
+
+### Temporal Types
+
+| C++ Type | Java Type | Python Type | Go Type |
+| ----------- | ----------- | --------------- | --------------- |
+| `Timestamp` | `Instant` | `datetime` | `time.Time` |
+| `Duration` | `Duration` | `timedelta` | `time.Duration` |
+| `LocalDate` | `LocalDate` | `datetime.date` | `time.Time` |
+
+## Field Order Requirements
+
+**Critical:** Field will be sorted by their snake_cased field name, converted
name must be considten across langauges
+
+### C++
+
+```cpp
+struct Person {
+ std::string name; // Field 0
+ int32_t age; // Field 1
+ std::string email; // Field 2
+};
+FORY_STRUCT(Person, name, age, email); // Order matters!
+```
+
+### Java
+
+```java
+public class Person {
+ public String name; // Field 0
+ public int age; // Field 1
+ public String email; // Field 2
+}
+```
+
+### Python
+
+```python
+class Person:
+ name: str # Field 0
+ age: int # Field 1
+ email: str # Field 2
+```
+
+## Type ID Consistency
+
+All languages must use the same type IDs:
+
+```cpp
+// C++
+fory.register_struct<Person>(100);
+fory.register_struct<Address>(101);
+fory.register_struct<Order>(102);
+```
+
+```java
+// Java
+fory.register(Person.class, 100);
+fory.register(Address.class, 101);
+fory.register(Order.class, 102);
+```
+
+```python
+# Python
+fory.register(Person, type_id=100)
+fory.register(Address, type_id=101)
+fory.register(Order, type_id=102)
+```
+
+## Compatible Mode
+
+For schema evolution across language boundaries:
+
+```cpp
+// C++ with compatible mode
+auto fory = Fory::builder()
+ .xlang(true)
+ .compatible(true) // Enable schema evolution
+ .build();
+```
+
+Compatible mode allows:
+
+- Adding new fields (with defaults)
+- Removing unused fields
+- Reordering fields
+
+## Troubleshooting
+
+### Type Mismatch Errors
+
+```
+Error: Type mismatch: expected 100, got 101
+```
+
+**Solution:** Ensure type IDs match across all languages.
+
+### Encoding Errors
+
+```
+Error: Invalid UTF-8 sequence
+```
+
+**Solution:** Ensure strings are valid UTF-8 in all languages.
+
+## Related Topics
+
+- [Configuration](configuration.md) - Builder options
+- [Type Registration](type-registration.md) - Registering types
+- [Supported Types](supported-types.md) - Type compatibility
diff --git a/docs/guide/cpp/index.md b/docs/guide/cpp/index.md
new file mode 100644
index 000000000..19c40d992
--- /dev/null
+++ b/docs/guide/cpp/index.md
@@ -0,0 +1,245 @@
+---
+title: C++ Serialization Guide
+sidebar_position: 0
+id: cpp_serialization_index
+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™** is a blazing fast multi-language serialization framework
powered by **JIT compilation** and **zero-copy** techniques, providing up to
**ultra-fast performance** while maintaining ease of use and safety.
+
+The C++ implementation provides high-performance serialization with
compile-time type safety using modern C++17 features and template
metaprogramming.
+
+## Why Apache Fory™ C++?
+
+- **🔥 Blazingly Fast**: Fast serialization and optimized binary protocols
+- **🌍 Cross-Language**: Seamlessly serialize/deserialize data across Java,
Python, C++, Go, JavaScript, and Rust
+- **🎯 Type-Safe**: Compile-time type checking with macro-based struct
registration
+- **🔄 Reference Tracking**: Automatic tracking of shared and circular
references
+- **📦 Schema Evolution**: Compatible mode for independent schema changes
+- **⚡ Two Modes**: Object graph serialization and zero-copy row-based format
+- **🧵 Thread Safety**: Both single-threaded (fastest) and thread-safe variants
+
+## Installation
+
+The C++ implementation supports both CMake and Bazel build systems.
+
+### Prerequisites
+
+- CMake 3.16+ (for CMake build) or Bazel 8+ (for Bazel build)
+- C++17 compatible compiler (GCC 7+, Clang 5+, MSVC 2017+)
+
+### Using CMake (Recommended)
+
+The easiest way to use Fory is with CMake's `FetchContent` module:
+
+```cmake
+cmake_minimum_required(VERSION 3.16)
+project(my_project LANGUAGES CXX)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+include(FetchContent)
+FetchContent_Declare(
+ fory
+ GIT_REPOSITORY https://github.com/apache/fory.git
+ GIT_TAG v0.14.0
+ SOURCE_SUBDIR cpp
+)
+FetchContent_MakeAvailable(fory)
+
+add_executable(my_app main.cc)
+target_link_libraries(my_app PRIVATE fory::serialization)
+```
+
+Then build and run:
+
+```bash
+mkdir build && cd build
+cmake .. -DCMAKE_BUILD_TYPE=Release
+cmake --build . --parallel
+./my_app
+```
+
+### Using Bazel
+
+Create a `MODULE.bazel` file in your project root:
+
+```bazel
+module(
+ name = "my_project",
+ version = "1.0.0",
+)
+
+bazel_dep(name = "rules_cc", version = "0.1.1")
+
+bazel_dep(name = "fory", version = "0.14.0")
+git_override(
+ module_name = "fory",
+ remote = "https://github.com/apache/fory.git",
+ commit = "v0.14.0", # Or use a specific commit hash for reproducibility
+)
+```
+
+Create a `BUILD` file for your application:
+
+```bazel
+cc_binary(
+ name = "my_app",
+ srcs = ["main.cc"],
+ deps = ["@fory//cpp/fory/serialization:fory_serialization"],
+)
+```
+
+Then build and run:
+
+```bash
+bazel build //:my_app
+bazel run //:my_app
+```
+
+For local development, you can use `local_path_override` instead:
+
+```bazel
+bazel_dep(name = "fory", version = "0.14.0")
+local_path_override(
+ module_name = "fory",
+ path = "/path/to/fory",
+)
+```
+
+### Examples
+
+See the [examples/cpp](https://github.com/apache/fory/tree/main/examples/cpp)
directory for complete working examples:
+
+-
[hello_world](https://github.com/apache/fory/tree/main/examples/cpp/hello_world)
- Object graph serialization
+- [hello_row](https://github.com/apache/fory/tree/main/examples/cpp/hello_row)
- Row format encoding
+
+## Quick Start
+
+### Basic Example
+
+```cpp
+#include "fory/serialization/fory.h"
+
+using namespace fory::serialization;
+
+// Define a struct
+struct Person {
+ std::string name;
+ int32_t age;
+ std::vector<std::string> hobbies;
+
+ bool operator==(const Person &other) const {
+ return name == other.name && age == other.age && hobbies == other.hobbies;
+ }
+};
+
+// Register the struct with Fory (must be in the same namespace)
+FORY_STRUCT(Person, name, age, hobbies);
+
+int main() {
+ // Create a Fory instance
+ auto fory = Fory::builder()
+ .xlang(true) // Enable cross-language mode
+ .track_ref(false) // Disable reference tracking for simple types
+ .build();
+
+ // Register the type with a unique ID
+ fory.register_struct<Person>(1);
+
+ // Create an object
+ Person person{"Alice", 30, {"reading", "coding"}};
+
+ // Serialize
+ auto result = fory.serialize(person);
+ if (!result.ok()) {
+ // Handle error
+ return 1;
+ }
+ std::vector<uint8_t> bytes = std::move(result).value();
+
+ // Deserialize
+ auto deser_result = fory.deserialize<Person>(bytes);
+ if (!deser_result.ok()) {
+ // Handle error
+ return 1;
+ }
+ Person decoded = std::move(deser_result).value();
+
+ assert(person == decoded);
+ return 0;
+}
+```
+
+## Thread Safety
+
+Apache Fory™ C++ provides two variants for different threading needs:
+
+### Single-Threaded (Fastest)
+
+```cpp
+// Single-threaded Fory - fastest, NOT thread-safe
+auto fory = Fory::builder()
+ .xlang(true)
+ .build();
+```
+
+### Thread-Safe
+
+```cpp
+// Thread-safe Fory - uses context pools
+auto fory = Fory::builder()
+ .xlang(true)
+ .build_thread_safe();
+
+// Can be used from multiple threads safely
+std::thread t1([&]() {
+ auto result = fory.serialize(obj1);
+});
+std::thread t2([&]() {
+ auto result = fory.serialize(obj2);
+});
+```
+
+**Tip:** Perform type registrations before spawning threads so every worker
sees the same metadata.
+
+## Use Cases
+
+### Object Serialization
+
+- Complex data structures with nested objects and references
+- Cross-language communication in microservices
+- General-purpose serialization with full type safety
+- Schema evolution with compatible mode
+
+### Row-Based Serialization
+
+- High-throughput data processing
+- Analytics workloads requiring fast field access
+- Memory-constrained environments
+- Zero-copy scenarios
+
+## Next Steps
+
+- [Configuration](configuration.md) - Builder options and modes
+- [Basic Serialization](basic-serialization.md) - Object graph serialization
+- [Schema Evolution](schema-evolution.md) - Compatible mode and schema changes
+- [Type Registration](type-registration.md) - Registering types
+- [Supported Types](supported-types.md) - All supported types
+- [Cross-Language](cross-language.md) - XLANG mode
+- [Row Format](row-format.md) - Zero-copy row-based format
diff --git a/docs/guide/cpp/row-format.md b/docs/guide/cpp/row-format.md
new file mode 100644
index 000000000..62e405dd4
--- /dev/null
+++ b/docs/guide/cpp/row-format.md
@@ -0,0 +1,516 @@
+---
+title: Row Format
+sidebar_position: 7
+id: cpp_row_format
+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.
+---
+
+This page covers the row-based serialization format for high-performance,
cache-friendly data access.
+
+## Overview
+
+Apache Fory™ Row Format is a binary format optimized for:
+
+- **Random Access**: Read any field without deserializing the entire object
+- **Zero-Copy**: Direct memory access without data copying
+- **Cache-Friendly**: Contiguous memory layout for CPU cache efficiency
+- **Columnar Conversion**: Easy conversion to Apache Arrow format
+- **Partial Serialization**: Serialize only needed fields
+
+## When to Use Row Format
+
+| Use Case | Row Format | Object Graph |
+| ----------------------------- | ---------- | ------------ |
+| Analytics/OLAP | ✅ | ❌ |
+| Random field access | ✅ | ❌ |
+| Full object serialization | ❌ | ✅ |
+| Complex object graphs | ❌ | ✅ |
+| Reference tracking | ❌ | ✅ |
+| Cross-language (simple types) | ✅ | ✅ |
+
+## Quick Start
+
+```cpp
+#include "fory/encoder/row_encoder.h"
+#include "fory/row/writer.h"
+
+using namespace fory::row;
+using namespace fory::row::encoder;
+
+// Define a struct
+struct Person {
+ int32_t id;
+ std::string name;
+ float score;
+};
+
+// Register field metadata (required for row encoding)
+FORY_FIELD_INFO(Person, id, name, score);
+
+int main() {
+ // Create encoder
+ RowEncoder<Person> encoder;
+
+ // Encode a person
+ Person person{1, "Alice", 95.5f};
+ encoder.Encode(person);
+
+ // Get the encoded row
+ auto row = encoder.GetWriter().ToRow();
+
+ // Random access to fields
+ int32_t id = row->GetInt32(0);
+ std::string name = row->GetString(1);
+ float score = row->GetFloat(2);
+
+ assert(id == 1);
+ assert(name == "Alice");
+ assert(score == 95.5f);
+
+ return 0;
+}
+```
+
+## Row Encoder
+
+### Basic Usage
+
+The `RowEncoder<T>` template class provides type-safe encoding:
+
+```cpp
+#include "fory/encoder/row_encoder.h"
+
+// Define struct with FORY_FIELD_INFO
+struct Point {
+ double x;
+ double y;
+};
+FORY_FIELD_INFO(Point, x, y);
+
+// Create encoder
+RowEncoder<Point> encoder;
+
+// Access schema (for inspection)
+const Schema& schema = encoder.GetSchema();
+std::cout << "Fields: " << schema.field_names().size() << std::endl;
+
+// Encode value
+Point p{1.0, 2.0};
+encoder.Encode(p);
+
+// Get result as Row
+auto row = encoder.GetWriter().ToRow();
+```
+
+### Nested Structs
+
+```cpp
+struct Address {
+ std::string city;
+ std::string country;
+};
+FORY_FIELD_INFO(Address, city, country);
+
+struct Person {
+ std::string name;
+ Address address;
+};
+FORY_FIELD_INFO(Person, name, address);
+
+// Encode nested struct
+RowEncoder<Person> encoder;
+Person person{"Alice", {"New York", "USA"}};
+encoder.Encode(person);
+
+auto row = encoder.GetWriter().ToRow();
+std::string name = row->GetString(0);
+
+// Access nested struct
+auto address_row = row->GetStruct(1);
+std::string city = address_row->GetString(0);
+std::string country = address_row->GetString(1);
+```
+
+### Arrays / Lists
+
+```cpp
+struct Record {
+ std::vector<int32_t> values;
+ std::string label;
+};
+FORY_FIELD_INFO(Record, values, label);
+
+RowEncoder<Record> encoder;
+Record record{{1, 2, 3, 4, 5}, "test"};
+encoder.Encode(record);
+
+auto row = encoder.GetWriter().ToRow();
+auto array = row->GetArray(0);
+
+int count = array->num_elements();
+for (int i = 0; i < count; i++) {
+ int32_t value = array->GetInt32(i);
+}
+```
+
+### Encoding Arrays Directly
+
+```cpp
+// Encode a vector directly (not inside a struct)
+std::vector<Person> people{
+ {"Alice", {"NYC", "USA"}},
+ {"Bob", {"London", "UK"}}
+};
+
+RowEncoder<decltype(people)> encoder;
+encoder.Encode(people);
+
+// Get array data
+auto array = encoder.GetWriter().CopyToArrayData();
+auto first_person = array->GetStruct(0);
+std::string first_name = first_person->GetString(0);
+```
+
+## Row Data Access
+
+### Row Class
+
+The `Row` class provides random access to struct fields:
+
+```cpp
+class Row {
+public:
+ // Null check
+ bool IsNullAt(int i) const;
+
+ // Primitive getters
+ bool GetBoolean(int i) const;
+ int8_t GetInt8(int i) const;
+ int16_t GetInt16(int i) const;
+ int32_t GetInt32(int i) const;
+ int64_t GetInt64(int i) const;
+ float GetFloat(int i) const;
+ double GetDouble(int i) const;
+
+ // String/binary getter
+ std::string GetString(int i) const;
+ std::vector<uint8_t> GetBinary(int i) const;
+
+ // Nested types
+ std::shared_ptr<Row> GetStruct(int i) const;
+ std::shared_ptr<ArrayData> GetArray(int i) const;
+ std::shared_ptr<MapData> GetMap(int i) const;
+
+ // Metadata
+ int num_fields() const;
+ SchemaPtr schema() const;
+
+ // Debug
+ std::string ToString() const;
+};
+```
+
+### ArrayData Class
+
+The `ArrayData` class provides access to list/array elements:
+
+```cpp
+class ArrayData {
+public:
+ // Null check
+ bool IsNullAt(int i) const;
+
+ // Element count
+ int num_elements() const;
+
+ // Primitive getters (same as Row)
+ int32_t GetInt32(int i) const;
+ // ... other primitives
+
+ // String getter
+ std::string GetString(int i) const;
+
+ // Nested types
+ std::shared_ptr<Row> GetStruct(int i) const;
+ std::shared_ptr<ArrayData> GetArray(int i) const;
+ std::shared_ptr<MapData> GetMap(int i) const;
+
+ // Type info
+ ListTypePtr type() const;
+};
+```
+
+### MapData Class
+
+The `MapData` class provides access to map key-value pairs:
+
+```cpp
+class MapData {
+public:
+ // Element count
+ int num_elements();
+
+ // Access keys and values as arrays
+ std::shared_ptr<ArrayData> keys_array();
+ std::shared_ptr<ArrayData> values_array();
+
+ // Type info
+ MapTypePtr type();
+};
+```
+
+## Schema and Types
+
+### Schema Definition
+
+Schemas define the structure of row data:
+
+```cpp
+#include "fory/row/schema.h"
+
+using namespace fory::row;
+
+// Create schema programmatically
+auto person_schema = schema({
+ field("id", int32()),
+ field("name", utf8()),
+ field("score", float32()),
+ field("active", boolean())
+});
+
+// Access schema info
+for (const auto& f : person_schema->fields()) {
+ std::cout << f->name() << ": " << f->type()->name() << std::endl;
+}
+```
+
+### Type System
+
+Available types for row format:
+
+```cpp
+// Primitive types
+DataTypePtr boolean(); // bool
+DataTypePtr int8(); // int8_t
+DataTypePtr int16(); // int16_t
+DataTypePtr int32(); // int32_t
+DataTypePtr int64(); // int64_t
+DataTypePtr float32(); // float
+DataTypePtr float64(); // double
+
+// String and binary
+DataTypePtr utf8(); // std::string
+DataTypePtr binary(); // std::vector<uint8_t>
+
+// Complex types
+DataTypePtr list(DataTypePtr element_type);
+DataTypePtr map(DataTypePtr key_type, DataTypePtr value_type);
+DataTypePtr struct_(std::vector<FieldPtr> fields);
+```
+
+### Type Inference
+
+The `RowEncodeTrait` template automatically infers types:
+
+```cpp
+// Type inference for primitives
+RowEncodeTrait<int32_t>::Type(); // Returns int32()
+RowEncodeTrait<float>::Type(); // Returns float32()
+RowEncodeTrait<std::string>::Type(); // Returns utf8()
+
+// Type inference for collections
+RowEncodeTrait<std::vector<int32_t>>::Type(); // Returns list(int32())
+
+// Type inference for maps
+RowEncodeTrait<std::map<std::string, int32_t>>::Type();
+// Returns map(utf8(), int32())
+
+// Type inference for structs (requires FORY_FIELD_INFO)
+RowEncodeTrait<Person>::Type(); // Returns struct_({...})
+RowEncodeTrait<Person>::Schema(); // Returns schema({...})
+```
+
+## Row Writer
+
+### RowWriter
+
+For manual row construction:
+
+```cpp
+#include "fory/row/writer.h"
+
+// Create schema
+auto my_schema = schema({
+ field("x", int32()),
+ field("y", float64()),
+ field("name", utf8())
+});
+
+// Create writer
+RowWriter writer(my_schema);
+writer.Reset();
+
+// Write fields
+writer.Write(0, 42); // x = 42
+writer.Write(1, 3.14); // y = 3.14
+writer.WriteString(2, "test"); // name = "test"
+
+// Get result
+auto row = writer.ToRow();
+```
+
+### ArrayWriter
+
+For manual array construction:
+
+```cpp
+// Create array type
+auto array_type = list(int32());
+
+// Create writer
+ArrayWriter writer(array_type);
+writer.Reset(5); // 5 elements
+
+// Write elements
+for (int i = 0; i < 5; i++) {
+ writer.Write(i, i * 10);
+}
+
+// Get result
+auto array = writer.CopyToArrayData();
+```
+
+### Null Values
+
+```cpp
+// Set null at specific index
+writer.SetNullAt(2); // Field 2 is null
+
+// Check null when reading
+if (!row->IsNullAt(2)) {
+ std::string value = row->GetString(2);
+}
+```
+
+## Memory Layout
+
+### Row Layout
+
+```
++------------------+--------------------+--------------------+
+| Null Bitmap | Fixed-Size Data | Variable-Size Data |
++------------------+--------------------+--------------------+
+| ceil(n/8) B | 8 * n bytes | variable |
++------------------+--------------------+--------------------+
+```
+
+- **Null Bitmap**: One bit per field, indicates null values
+- **Fixed-Size Data**: 8 bytes per field (primitives stored directly,
offset+size for variable)
+- **Variable-Size Data**: Strings, arrays, nested structs
+
+### Array Layout
+
+```
++------------+------------------+--------------------+--------------------+
+| Num Elems | Null Bitmap | Fixed-Size Data | Variable-Size Data |
++------------+------------------+--------------------+--------------------+
+| 8 bytes | ceil(n/8) bytes | elem_size * n | variable |
++------------+------------------+--------------------+--------------------+
+```
+
+### Map Layout
+
+```
++------------------+------------------+
+| Keys Array | Values Array |
++------------------+------------------+
+```
+
+## Performance Tips
+
+### 1. Reuse Encoders
+
+```cpp
+RowEncoder<Person> encoder;
+
+// Encode multiple records
+for (const auto& person : people) {
+ encoder.Encode(person);
+ auto row = encoder.GetWriter().ToRow();
+ // Process row...
+}
+```
+
+### 2. Pre-allocate Buffer
+
+```cpp
+// Get buffer reference for pre-allocation
+auto& buffer = encoder.GetWriter().buffer();
+buffer->Reserve(expected_size);
+```
+
+### 3. Batch Processing
+
+```cpp
+// Process in batches for better cache utilization
+std::vector<Person> batch;
+batch.reserve(BATCH_SIZE);
+
+while (hasMore()) {
+ batch.clear();
+ fillBatch(batch);
+
+ for (const auto& person : batch) {
+ encoder.Encode(person);
+ process(encoder.GetWriter().ToRow());
+ }
+}
+```
+
+### 4. Zero-Copy Reading
+
+```cpp
+// Point to existing buffer (zero-copy)
+Row row(schema);
+row.PointTo(buffer, offset, size);
+
+// Access fields directly from buffer
+int32_t id = row.GetInt32(0);
+```
+
+## Supported Types Summary
+
+| C++ Type | Row Type | Fixed Size |
+| ------------------------ | ---------------- | ---------- |
+| `bool` | `boolean()` | 1 byte |
+| `int8_t` | `int8()` | 1 byte |
+| `int16_t` | `int16()` | 2 bytes |
+| `int32_t` | `int32()` | 4 bytes |
+| `int64_t` | `int64()` | 8 bytes |
+| `float` | `float32()` | 4 bytes |
+| `double` | `float64()` | 8 bytes |
+| `std::string` | `utf8()` | Variable |
+| `std::vector<T>` | `list(T)` | Variable |
+| `std::map<K,V>` | `map(K,V)` | Variable |
+| `std::optional<T>` | Inner type | Nullable |
+| Struct (FORY_FIELD_INFO) | `struct_({...})` | Variable |
+
+## Related Topics
+
+- [Basic Serialization](basic-serialization.md) - Object graph serialization
+- [Configuration](configuration.md) - Builder options
+- [Supported Types](supported-types.md) - All supported types
diff --git a/docs/guide/cpp/schema-evolution.md
b/docs/guide/cpp/schema-evolution.md
new file mode 100644
index 000000000..21e6a5098
--- /dev/null
+++ b/docs/guide/cpp/schema-evolution.md
@@ -0,0 +1,404 @@
+---
+title: Schema Evolution
+sidebar_position: 3
+id: cpp_schema_evolution
+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 schema evolution in **Compatible mode**, allowing
serialization and deserialization peers to have different type definitions.
+
+## Compatible Mode
+
+Enable schema evolution with `compatible(true)`:
+
+```cpp
+#include "fory/serialization/fory.h"
+
+using namespace fory::serialization;
+
+// Version 1: Original schema
+struct PersonV1 {
+ std::string name;
+ int32_t age;
+};
+FORY_STRUCT(PersonV1, name, age);
+
+// Version 2: Added email field
+struct PersonV2 {
+ std::string name;
+ int32_t age;
+ std::string email; // NEW FIELD
+};
+FORY_STRUCT(PersonV2, name, age, email);
+
+int main() {
+ // Create separate Fory instances for each schema version
+ auto fory_v1 = Fory::builder()
+ .compatible(true) // Enable schema evolution
+ .xlang(true)
+ .build();
+
+ auto fory_v2 = Fory::builder()
+ .compatible(true)
+ .xlang(true)
+ .build();
+
+ // Register with the SAME type ID for schema evolution
+ constexpr uint32_t PERSON_TYPE_ID = 100;
+ fory_v1.register_struct<PersonV1>(PERSON_TYPE_ID);
+ fory_v2.register_struct<PersonV2>(PERSON_TYPE_ID);
+
+ // Serialize with V1
+ PersonV1 v1{"Alice", 30};
+ auto bytes = fory_v1.serialize(v1).value();
+
+ // Deserialize as V2 - email gets default value (empty string)
+ auto v2 = fory_v2.deserialize<PersonV2>(bytes).value();
+ assert(v2.name == "Alice");
+ assert(v2.age == 30);
+ assert(v2.email == ""); // Default value for missing field
+
+ return 0;
+}
+```
+
+## Schema Evolution Features
+
+Compatible mode supports the following schema changes:
+
+| Change Type | Support | Behavior |
+| ------------------ | ------- | --------------------------------------- |
+| Add new fields | ✅ | Missing fields use default values |
+| Remove fields | ✅ | Extra fields are skipped |
+| Reorder fields | ✅ | Fields matched by name, not position |
+| Change nullability | ✅ | `T` ↔ `std::optional<T>` |
+| Change field types | ❌ | Types must be compatible |
+| Rename fields | ❌ | Field names must match (case-sensitive) |
+
+## Adding Fields (Backward Compatibility)
+
+When deserializing old data with a new schema that has additional fields:
+
+```cpp
+// Old schema (V1)
+struct ProductV1 {
+ std::string name;
+ double price;
+};
+FORY_STRUCT(ProductV1, name, price);
+
+// New schema (V2) with additional fields
+struct ProductV2 {
+ std::string name;
+ double price;
+ std::vector<std::string> tags; // NEW
+ std::map<std::string, std::string> attributes; // NEW
+};
+FORY_STRUCT(ProductV2, name, price, tags, attributes);
+
+// Serialize V1
+ProductV1 v1{"Laptop", 999.99};
+auto bytes = fory_v1.serialize(v1).value();
+
+// Deserialize as V2
+auto v2 = fory_v2.deserialize<ProductV2>(bytes).value();
+assert(v2.name == "Laptop");
+assert(v2.price == 999.99);
+assert(v2.tags.empty()); // Default: empty vector
+assert(v2.attributes.empty()); // Default: empty map
+```
+
+## Removing Fields (Forward Compatibility)
+
+When deserializing new data with an old schema that has fewer fields:
+
+```cpp
+// Full schema
+struct UserFull {
+ int64_t id;
+ std::string username;
+ std::string email;
+ std::string password_hash;
+ int32_t login_count;
+};
+FORY_STRUCT(UserFull, id, username, email, password_hash, login_count);
+
+// Minimal schema (removed 3 fields)
+struct UserMinimal {
+ int64_t id;
+ std::string username;
+};
+FORY_STRUCT(UserMinimal, id, username);
+
+// Serialize full version
+UserFull full{12345, "johndoe", "[email protected]", "hash123", 42};
+auto bytes = fory_full.serialize(full).value();
+
+// Deserialize as minimal - extra fields are skipped
+auto minimal = fory_minimal.deserialize<UserMinimal>(bytes).value();
+assert(minimal.id == 12345);
+assert(minimal.username == "johndoe");
+// email, password_hash, login_count are skipped
+```
+
+## Field Reordering
+
+In compatible mode, fields are matched by name, not by position:
+
+```cpp
+// Original field order
+struct ConfigOriginal {
+ std::string host;
+ int32_t port;
+ bool enable_ssl;
+ std::string protocol;
+};
+FORY_STRUCT(ConfigOriginal, host, port, enable_ssl, protocol);
+
+// Reordered fields
+struct ConfigReordered {
+ bool enable_ssl; // Moved to first
+ std::string protocol; // Moved to second
+ std::string host; // Moved to third
+ int32_t port; // Moved to last
+};
+FORY_STRUCT(ConfigReordered, enable_ssl, protocol, host, port);
+
+// Serialize with original order
+ConfigOriginal orig{"localhost", 8080, true, "https"};
+auto bytes = fory_orig.serialize(orig).value();
+
+// Deserialize with different field order - works correctly
+auto reordered = fory_reord.deserialize<ConfigReordered>(bytes).value();
+assert(reordered.host == "localhost");
+assert(reordered.port == 8080);
+assert(reordered.enable_ssl == true);
+assert(reordered.protocol == "https");
+```
+
+## Nested Struct Evolution
+
+Schema evolution works recursively for nested structs:
+
+```cpp
+// V1 Address
+struct AddressV1 {
+ std::string street;
+ std::string city;
+};
+FORY_STRUCT(AddressV1, street, city);
+
+// V2 Address with new fields
+struct AddressV2 {
+ std::string street;
+ std::string city;
+ std::string country; // NEW
+ std::string zipcode; // NEW
+};
+FORY_STRUCT(AddressV2, street, city, country, zipcode);
+
+// V1 Employee with V1 Address
+struct EmployeeV1 {
+ std::string name;
+ AddressV1 home_address;
+};
+FORY_STRUCT(EmployeeV1, name, home_address);
+
+// V2 Employee with V2 Address and new field
+struct EmployeeV2 {
+ std::string name;
+ AddressV2 home_address; // Nested struct evolved
+ std::string employee_id; // NEW
+};
+FORY_STRUCT(EmployeeV2, name, home_address, employee_id);
+
+// Register types with same IDs
+constexpr uint32_t ADDRESS_TYPE_ID = 100;
+constexpr uint32_t EMPLOYEE_TYPE_ID = 101;
+
+fory_v1.register_struct<AddressV1>(ADDRESS_TYPE_ID);
+fory_v1.register_struct<EmployeeV1>(EMPLOYEE_TYPE_ID);
+fory_v2.register_struct<AddressV2>(ADDRESS_TYPE_ID);
+fory_v2.register_struct<EmployeeV2>(EMPLOYEE_TYPE_ID);
+
+// Serialize V1
+EmployeeV1 emp_v1{"Jane Doe", {"123 Main St", "NYC"}};
+auto bytes = fory_v1.serialize(emp_v1).value();
+
+// Deserialize as V2
+auto emp_v2 = fory_v2.deserialize<EmployeeV2>(bytes).value();
+assert(emp_v2.name == "Jane Doe");
+assert(emp_v2.home_address.street == "123 Main St");
+assert(emp_v2.home_address.city == "NYC");
+assert(emp_v2.home_address.country == ""); // Default
+assert(emp_v2.home_address.zipcode == ""); // Default
+assert(emp_v2.employee_id == ""); // Default
+```
+
+## Bidirectional Evolution
+
+Schema evolution works in both directions:
+
+```cpp
+// V2 -> V1 (downgrade)
+PersonV2 v2{"Charlie", 35, "[email protected]"};
+auto bytes = fory_v2.serialize(v2).value();
+
+auto v1 = fory_v1.deserialize<PersonV1>(bytes).value();
+assert(v1.name == "Charlie");
+assert(v1.age == 35);
+// email field is discarded during deserialization
+```
+
+## Default Values
+
+When fields are missing, C++ default initialization is used:
+
+| Type | Default Value |
+| ---------------------- | ------------------- |
+| `int8_t`, `int16_t`... | `0` |
+| `float`, `double` | `0.0` |
+| `bool` | `false` |
+| `std::string` | `""` |
+| `std::vector<T>` | Empty vector |
+| `std::map<K,V>` | Empty map |
+| `std::set<T>` | Empty set |
+| `std::optional<T>` | `std::nullopt` |
+| Struct types | Default-constructed |
+
+## Schema Consistent Mode (Default)
+
+Without compatible mode, schemas must match exactly:
+
+```cpp
+// Strict mode (default)
+auto fory = Fory::builder()
+ .compatible(false) // Default: schema must match
+ .xlang(true)
+ .build();
+
+// Serialization/deserialization requires identical schemas
+// Schema mismatches may cause errors or undefined behavior
+```
+
+**Use SchemaConsistent mode when:**
+
+- Schemas are guaranteed to match (same binary version)
+- Maximum performance is required (less metadata overhead)
+- You control both serialization and deserialization
+
+**Use Compatible mode when:**
+
+- Schemas may evolve independently
+- Cross-version compatibility is required
+- Different services may have different schema versions
+
+## Type ID Requirements
+
+For schema evolution to work:
+
+1. **Same Type ID**: Different versions of the same struct must use the same
type ID
+2. **Consistent IDs**: Type IDs must be consistent across all Fory instances
+3. **Register All Versions**: Each Fory instance registers its own struct
version
+
+```cpp
+constexpr uint32_t PERSON_TYPE_ID = 100;
+
+// Instance 1 uses PersonV1
+fory_v1.register_struct<PersonV1>(PERSON_TYPE_ID);
+
+// Instance 2 uses PersonV2
+fory_v2.register_struct<PersonV2>(PERSON_TYPE_ID);
+
+// Same type ID enables schema evolution
+```
+
+## Best Practices
+
+### 1. Plan for Evolution
+
+Design schemas with future changes in mind:
+
+```cpp
+// Good: Use optional for fields that might be removed
+struct Config {
+ std::string host;
+ int32_t port;
+ std::optional<std::string> deprecated_field; // Can be removed later
+};
+```
+
+### 2. Use Meaningful Default Values
+
+Consider what default values make sense for new fields:
+
+```cpp
+struct Settings {
+ int32_t timeout_ms; // Default: 0 (might want a sensible default)
+ bool enabled; // Default: false
+ std::string mode; // Default: "" (might want "default")
+};
+```
+
+### 3. Document Schema Versions
+
+Track schema changes for debugging:
+
+```cpp
+// V1: Initial schema (2024-01-01)
+// V2: Added email field (2024-02-01)
+// V3: Added phone, address fields (2024-03-01)
+```
+
+### 4. Test Evolution Paths
+
+Test both upgrade and downgrade scenarios:
+
+```cpp
+// Test V1 -> V2
+// Test V2 -> V1
+// Test V1 -> V3
+// Test V3 -> V1
+```
+
+## Cross-Language Schema Evolution
+
+Schema evolution works across languages when using xlang mode:
+
+```cpp
+// C++ with compatible mode
+auto fory = Fory::builder()
+ .compatible(true)
+ .xlang(true)
+ .build();
+```
+
+```java
+// Java with compatible mode
+Fory fory = Fory.builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withLanguage(Language.XLANG)
+ .build();
+```
+
+Both instances can exchange data even with different schema versions.
+
+## Related Topics
+
+- [Configuration](configuration.md) - Enabling compatible mode
+- [Type Registration](type-registration.md) - Type ID management
+- [Cross-Language](cross-language.md) - Cross-language considerations
diff --git a/docs/guide/cpp/supported-types.md
b/docs/guide/cpp/supported-types.md
new file mode 100644
index 000000000..fead3785b
--- /dev/null
+++ b/docs/guide/cpp/supported-types.md
@@ -0,0 +1,277 @@
+---
+title: Supported Types
+sidebar_position: 5
+id: cpp_supported_types
+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.
+---
+
+This page documents all types supported by Fory C++ serialization.
+
+## Primitive Types
+
+All C++ primitive types are supported with efficient binary encoding:
+
+| Type | Size | Fory TypeId | Notes |
+| ---------- | ------ | ----------- | --------------------- |
+| `bool` | 1 byte | BOOL | True/false |
+| `int8_t` | 1 byte | INT8 | Signed byte |
+| `uint8_t` | 1 byte | INT8 | Unsigned byte |
+| `int16_t` | 2 byte | INT16 | Signed short |
+| `uint16_t` | 2 byte | INT16 | Unsigned short |
+| `int32_t` | 4 byte | INT32 | Signed integer |
+| `uint32_t` | 4 byte | INT32 | Unsigned integer |
+| `int64_t` | 8 byte | INT64 | Signed long |
+| `uint64_t` | 8 byte | INT64 | Unsigned long |
+| `float` | 4 byte | FLOAT32 | IEEE 754 single |
+| `double` | 8 byte | FLOAT64 | IEEE 754 double |
+| `char` | 1 byte | INT8 | Character (as signed) |
+| `char16_t` | 2 byte | INT16 | 16-bits characters |
+| `char32_t` | 4 byte | INT32 | 32-bits characters |
+
+```cpp
+int32_t value = 42;
+auto bytes = fory.serialize(value).value();
+auto decoded = fory.deserialize<int32_t>(bytes).value();
+assert(value == decoded);
+```
+
+## String Types
+
+| Type | Fory TypeId | Notes |
+| ------------------ | ----------- | ------------------------ |
+| `std::string` | STRING | UTF-8 encoded |
+| `std::string_view` | STRING | Zero-copy view (read) |
+| `std::u16string` | STRING | UTF-16 (converted) |
+| `binary` | BINARY | Raw bytes without length |
+
+```cpp
+std::string text = "Hello, World!";
+auto bytes = fory.serialize(text).value();
+auto decoded = fory.deserialize<std::string>(bytes).value();
+assert(text == decoded);
+```
+
+## Collection Types
+
+### Vector / List
+
+`std::vector<T>` for any serializable element type:
+
+```cpp
+std::vector<int32_t> numbers{1, 2, 3, 4, 5};
+auto bytes = fory.serialize(numbers).value();
+auto decoded = fory.deserialize<std::vector<int32_t>>(bytes).value();
+
+// Nested vectors
+std::vector<std::vector<std::string>> nested{
+ {"a", "b"},
+ {"c", "d", "e"}
+};
+```
+
+### Set
+
+`std::set<T>` and `std::unordered_set<T>`:
+
+```cpp
+std::set<std::string> tags{"cpp", "serialization", "fory"};
+auto bytes = fory.serialize(tags).value();
+auto decoded = fory.deserialize<std::set<std::string>>(bytes).value();
+
+std::unordered_set<int32_t> ids{1, 2, 3};
+```
+
+### Map
+
+`std::map<K, V>` and `std::unordered_map<K, V>`:
+
+```cpp
+std::map<std::string, int32_t> scores{
+ {"Alice", 100},
+ {"Bob", 95}
+};
+auto bytes = fory.serialize(scores).value();
+auto decoded = fory.deserialize<std::map<std::string, int32_t>>(bytes).value();
+
+// Unordered map
+std::unordered_map<int32_t, std::string> lookup{
+ {1, "one"},
+ {2, "two"}
+};
+```
+
+## Smart Pointers
+
+### std::optional
+
+Nullable wrapper for any type:
+
+```cpp
+std::optional<int32_t> maybe_value = 42;
+std::optional<int32_t> empty_value = std::nullopt;
+
+auto bytes = fory.serialize(maybe_value).value();
+auto decoded = fory.deserialize<std::optional<int32_t>>(bytes).value();
+assert(decoded.has_value() && *decoded == 42);
+```
+
+### std::shared_ptr
+
+Shared ownership with reference tracking:
+
+```cpp
+auto shared = std::make_shared<Person>("Alice", 30);
+
+auto bytes = fory.serialize(shared).value();
+auto decoded = fory.deserialize<std::shared_ptr<Person>>(bytes).value();
+```
+
+**With reference tracking enabled (`track_ref(true)`):**
+
+- Shared objects are serialized once
+- References to the same object are preserved
+- Circular references are handled automatically
+
+### std::unique_ptr
+
+Exclusive ownership:
+
+```cpp
+auto unique = std::make_unique<Person>("Bob", 25);
+
+auto bytes = fory.serialize(unique).value();
+auto decoded = fory.deserialize<std::unique_ptr<Person>>(bytes).value();
+```
+
+## Variant Type
+
+`std::variant<Ts...>` for type-safe unions:
+
+```cpp
+using MyVariant = std::variant<int32_t, std::string, double>;
+
+MyVariant v1 = 42;
+MyVariant v2 = std::string("hello");
+MyVariant v3 = 3.14;
+
+auto bytes = fory.serialize(v1).value();
+auto decoded = fory.deserialize<MyVariant>(bytes).value();
+assert(std::get<int32_t>(decoded) == 42);
+```
+
+### std::monostate
+
+Empty variant alternative:
+
+```cpp
+using OptionalInt = std::variant<std::monostate, int32_t>;
+
+OptionalInt empty = std::monostate{};
+OptionalInt value = 42;
+```
+
+## Temporal Types
+
+### Duration
+
+`std::chrono::nanoseconds`:
+
+```cpp
+using Duration = std::chrono::nanoseconds;
+
+Duration d = std::chrono::seconds(30);
+auto bytes = fory.serialize(d).value();
+auto decoded = fory.deserialize<Duration>(bytes).value();
+```
+
+### Timestamp
+
+Point in time since Unix epoch:
+
+```cpp
+using Timestamp = std::chrono::time_point<std::chrono::system_clock,
+ std::chrono::nanoseconds>;
+
+Timestamp now = std::chrono::system_clock::now();
+auto bytes = fory.serialize(now).value();
+auto decoded = fory.deserialize<Timestamp>(bytes).value();
+```
+
+### LocalDate
+
+Days since Unix epoch:
+
+```cpp
+LocalDate date{18628}; // Days since 1970-01-01
+
+auto bytes = fory.serialize(date).value();
+auto decoded = fory.deserialize<LocalDate>(bytes).value();
+```
+
+## User-Defined Structs
+
+Any struct can be made serializable with `FORY_STRUCT`:
+
+```cpp
+struct Point {
+ double x;
+ double y;
+ double z;
+};
+FORY_STRUCT(Point, x, y, z);
+
+struct Line {
+ Point start;
+ Point end;
+ std::string label;
+};
+FORY_STRUCT(Line, start, end, label);
+```
+
+## Enum Types
+
+Both scoped and unscoped enums are supported with `FORY_ENUM`:
+
+```cpp
+// Scoped enum (C++11 enum class)
+enum class Color { RED = 0, GREEN = 1, BLUE = 2 };
+
+// Unscoped enum with incontinuous values
+enum Priority : int32_t { LOW = -10, NORMAL = 0, HIGH = 10 };
+FORY_ENUM(Priority, LOW, NORMAL, HIGH);
+
+// Usage
+Color c = Color::GREEN;
+auto bytes = fory.serialize(c).value();
+auto decoded = fory.deserialize<Color>(bytes).value();
+```
+
+## Unsupported Types
+
+Currently not supported:
+
+- Raw pointers (`T*`) - use smart pointers instead
+- `std::tuple<Ts...>` - use structs instead
+- `std::array<T, N>` - use `std::vector<T>` instead
+- Function pointers
+- References (`T&`, `const T&`) - by value only
+
+## Related Topics
+
+- [Basic Serialization](basic-serialization.md) - Using these types
+- [Type Registration](type-registration.md) - Registering types
+- [Cross-Language](cross-language.md) - Cross-language compatibility
diff --git a/docs/guide/cpp/type-registration.md
b/docs/guide/cpp/type-registration.md
new file mode 100644
index 000000000..7b930c060
--- /dev/null
+++ b/docs/guide/cpp/type-registration.md
@@ -0,0 +1,225 @@
+---
+title: Type Registration
+sidebar_position: 4
+id: cpp_type_registration
+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.
+---
+
+This page explains how to register types for serialization.
+
+## Overview
+
+Apache Fory™ requires explicit type registration for struct types. This design
enables:
+
+- **Cross-Language Compatibility**: Registered type IDs are used across
language boundaries
+- **Type Safety**: Detects type mismatches at deserialization time
+- **Polymorphic Serialization**: Enables serialization of polymorphic objects
via smart pointers
+
+## Registering Structs
+
+Use `register_struct<T>(type_id)` to register a struct type:
+
+```cpp
+#include "fory/serialization/fory.h"
+
+using namespace fory::serialization;
+
+struct Person {
+ std::string name;
+ int32_t age;
+};
+FORY_STRUCT(Person, name, age);
+
+int main() {
+ auto fory = Fory::builder().xlang(true).build();
+
+ // Register with a unique type ID
+ fory.register_struct<Person>(100);
+
+ Person person{"Alice", 30};
+ auto bytes = fory.serialize(person).value();
+ auto decoded = fory.deserialize<Person>(bytes).value();
+}
+```
+
+## Type ID Guidelines
+
+Type IDs must be:
+
+1. **Unique**: Each type must have a unique ID within a Fory instance
+2. **Consistent**: Same ID must be used across all languages and versions
+
+User-registered type IDs are in a separate namespace from built-in type IDs,
so you can start from 0:
+
+```cpp
+// User type IDs can start from 0
+fory.register_struct<Address>(0);
+fory.register_struct<Person>(1);
+fory.register_struct<Order>(2);
+```
+
+## Registering Enums
+
+For simple enums with continuous values starting from 0, no macro is needed:
+
+```cpp
+// Simple continuous enum - no FORY_ENUM needed
+enum class Color { RED, GREEN, BLUE }; // Values: 0, 1, 2
+
+// Just register directly
+fory.register_struct<Color>(0);
+```
+
+For enums with non-continuous values, use the `FORY_ENUM` macro to map values
to ordinals:
+
+```cpp
+// Non-continuous enum values - FORY_ENUM required
+enum class Priority { LOW = 10, MEDIUM = 50, HIGH = 100 };
+FORY_ENUM(Priority, LOW, MEDIUM, HIGH);
+
+// Global namespace enum (prefix with ::)
+enum LegacyStatus { UNKNOWN = -1, OK = 0, ERROR = 1 };
+FORY_ENUM(::LegacyStatus, UNKNOWN, OK, ERROR);
+
+// Register after FORY_ENUM
+fory.register_struct<Priority>(1);
+fory.register_struct<LegacyStatus>(2);
+```
+
+**When to use `FORY_ENUM`:**
+
+- Enum values don't start from 0
+- Enum values are not continuous (e.g., 10, 50, 100)
+- You need name-to-value mapping at compile time
+
+## Thread-Safe Registration
+
+For `ThreadSafeFory`, register types before spawning threads:
+
+```cpp
+auto fory = Fory::builder().xlang(true).build_thread_safe();
+
+// Register all types first
+fory.register_struct<TypeA>(100);
+fory.register_struct<TypeB>(101);
+
+// Now safe to use from multiple threads
+std::thread t1([&]() {
+ auto result = fory.serialize(obj_a);
+});
+std::thread t2([&]() {
+ auto result = fory.serialize(obj_b);
+});
+```
+
+## Cross-Language Registration
+
+For cross-language compatibility, ensure:
+
+1. **Same Type ID**: Use identical IDs in all languages
+2. **Compatible Types**: Use equivalent types across languages
+
+### Java
+
+```java
+Fory fory = Fory.builder().build();
+fory.register(Person.class, 100);
+fory.register(Address.class, 101);
+```
+
+### Python
+
+```python
+import pyfory
+
+fory = pyfory.Fory()
+fory.register(Person, 100)
+fory.register(Address, 101)
+```
+
+### C++
+
+```cpp
+auto fory = Fory::builder().xlang(true).build();
+fory.register_struct<Person>(100);
+fory.register_struct<Address>(101);
+```
+
+## Built-in Type IDs
+
+Built-in types have pre-assigned type IDs and don't need registration:
+
+| Type ID | Type |
+| ------- | -------------------- |
+| 0 | NONE |
+| 1 | BOOL |
+| 2 | INT8 |
+| 3 | INT16 |
+| 4 | INT32 |
+| 5 | VAR_INT32 |
+| 6 | INT64 |
+| 7 | VAR_INT64 |
+| 8 | SLI_INT64 |
+| 9 | FLOAT16 |
+| 10 | FLOAT32 |
+| 11 | FLOAT64 |
+| 12 | STRING |
+| 13 | LIST |
+| 14 | MAP |
+| 15 | SET |
+| 16 | TIMESTAMP |
+| 17 | DURATION |
+| 18 | LOCAL_DATE |
+| 19 | DECIMAL |
+| 20 | BINARY |
+| 21 | ARRAY |
+| 22 | BOOL_ARRAY |
+| 23-28 | INT_ARRAY variants |
+| 29-31 | FLOAT_ARRAY variants |
+| 32 | STRUCT |
+| 33 | COMPATIBLE_STRUCT |
+| 34 | NAMED_STRUCT |
+| 35 | NAMED*COMPATIBLE*... |
+| 36 | EXT |
+| 37 | NAMED_EXT |
+| 63 | UNKNOWN |
+
+## Error Handling
+
+Registration errors are checked at serialization/deserialization time:
+
+```cpp
+// Attempting to serialize unregistered type
+auto result = fory.serialize(unregistered_obj);
+if (!result.ok()) {
+ // Error: "Type not registered: ..."
+ std::cerr << result.error().to_string() << std::endl;
+}
+
+// Type ID mismatch during deserialization
+auto result = fory.deserialize<WrongType>(bytes);
+if (!result.ok()) {
+ // Error: "Type mismatch: expected X, got Y"
+ std::cerr << result.error().to_string() << std::endl;
+}
+```
+
+## Related Topics
+
+- [Basic Serialization](basic-serialization.md) - Using registered types
+- [Cross-Language](cross-language.md) - Cross-language considerations
+- [Supported Types](supported-types.md) - All supported types
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]