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 b1725938d feat(c++): support private fields of c++ class (#3193)
b1725938d is described below
commit b1725938d8bfc1ef2dd84b53c54018a8e853cdd6
Author: Shawn Yang <[email protected]>
AuthorDate: Sun Jan 25 19:05:02 2026 +0800
feat(c++): support private fields of c++ class (#3193)
## Why?
The previous `FORY_FIELD_INFO` macro required defining field metadata
outside the class/struct definition, which prevented it from accessing
private fields. This limitation made it impossible to serialize classes
with private members unless they were made public or friends were added.
## What does this PR do?
This PR introduces a new `FORY_STRUCT` macro that must be used inside
the class/struct definition as a replacement for `FORY_FIELD_INFO`. The
key changes include:
1. **New `FORY_STRUCT` macro**: Replaces `FORY_FIELD_INFO` and must be
placed inside the struct/class body, enabling access to private fields
via hidden friend functions
2. **Hidden friend function approach**: Uses ADL (Argument-Dependent
Lookup) to define `ForyFieldInfo()` as a friend function within the
class, granting access to private members
3. **Backward compatibility**: Keeps `FORY_STRUCT_FIELDS` for internal
use and provides empty struct support via `FORY_STRUCT_EMPTY`
4. **Comprehensive migration**: Updates all usages across:
- Test files (encoder, serialization, field tests)
- Documentation (C++ row format guide, xlang spec)
- Examples (hello_row example)
- README files
**Migration pattern:**
```cpp
// Before:
class MyClass {
int private_field;
};
FORY_FIELD_INFO(MyClass, private_field); // Outside class, no access to
private
// After:
class MyClass {
int private_field;
FORY_STRUCT(MyClass, private_field); // Inside class, can access private
};
```
For external struct:
```cpp
namespace thirdparty {
struct Foo {
int32_t id;
std::string name;
};
FORY_STRUCT(Foo, id, name);
} // namespace thirdparty
auto fory = Fory::builder().build();
fory->register_struct<thirdparty::Foo>(1);
```
To include base-class fields in a derived type, list `FORY_BASE(Base)`
inside
`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields
can be
referenced.
```cpp
struct Base {
int32_t id;
FORY_STRUCT(Base, id);
};
struct Derived : Base {
std::string name;
FORY_STRUCT(Derived, FORY_BASE(Base), name);
};
```
## Related issues
#2906
## Does this PR introduce any user-facing change?
Yes, this introduces a breaking API change requiring users to migrate
from `FORY_FIELD_INFO` to `FORY_STRUCT`.
- [x] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
No performance impact expected - this is a compile-time macro change
that does not affect runtime serialization logic.
---
.github/workflows/ci.yml | 9 +
.gitignore | 1 +
benchmarks/cpp_benchmark/README.md | 16 +-
compiler/README.md | 4 +-
compiler/fory_compiler/generators/cpp.py | 24 +-
cpp/README.md | 49 ++-
cpp/fory/encoder/row_encode_trait.h | 6 +-
cpp/fory/encoder/row_encode_trait_test.cc | 21 +-
cpp/fory/encoder/row_encoder_test.cc | 85 +++++-
cpp/fory/meta/field_info.h | 332 ++++++++++++++++++---
cpp/fory/meta/field_info_test.cc | 18 +-
cpp/fory/meta/field_test.cc | 16 +-
cpp/fory/meta/preprocessor.h | 18 ++
cpp/fory/row/schema.h | 2 +-
.../serialization/collection_serializer_test.cc | 28 +-
cpp/fory/serialization/field_serializer_test.cc | 38 +--
cpp/fory/serialization/fory.h | 6 +-
cpp/fory/serialization/map_serializer_test.cc | 15 +-
cpp/fory/serialization/serialization_test.cc | 11 +-
cpp/fory/serialization/serializer_traits.h | 6 +-
.../serialization/smart_ptr_serializer_test.cc | 26 +-
cpp/fory/serialization/struct_compatible_test.cc | 26 +-
cpp/fory/serialization/struct_serializer.h | 101 ++-----
cpp/fory/serialization/struct_test.cc | 139 +++++++--
cpp/fory/serialization/tuple_serializer_test.cc | 14 +-
cpp/fory/serialization/type_resolver.h | 4 +-
cpp/fory/serialization/unsigned_serializer_test.cc | 6 +-
cpp/fory/serialization/variant_serializer_test.cc | 4 +-
cpp/fory/serialization/weak_ptr_serializer.h | 2 +-
cpp/fory/serialization/weak_ptr_serializer_test.cc | 10 +-
cpp/fory/serialization/xlang_test_main.cc | 92 +++---
cpp/fory/util/pool_test.cc | 4 +-
docs/benchmarks/cpp/README.md | 32 +-
docs/benchmarks/cpp/throughput.png | Bin 48028 -> 44522 bytes
docs/guide/cpp/basic-serialization.md | 85 +++++-
docs/guide/cpp/index.md | 18 ++
docs/guide/cpp/row-format.md | 44 ++-
docs/guide/java/row-format.md | 6 +-
docs/guide/python/row-format.md | 6 +-
docs/specification/xlang_serialization_spec.md | 2 +-
examples/cpp/hello_row/README.md | 7 +-
examples/cpp/hello_row/main.cc | 7 +-
examples/cpp/hello_world/README.md | 3 +-
examples/cpp/hello_world/main.cc | 13 +-
java/fory-format/pom.xml | 7 +
python/README.md | 6 +-
46 files changed, 931 insertions(+), 438 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0f8e5d1e8..a9028cb28 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -380,6 +380,15 @@ jobs:
key: bazel-build-cpp-${{ runner.os }}-${{ runner.arch }}-${{
hashFiles('cpp/**', 'BUILD', 'WORKSPACE') }}
- name: Run C++ CI with Bazel
run: python ./ci/run_ci.py cpp
+ - name: Upload Bazel Test Logs
+ uses: actions/upload-artifact@v4
+ if: ${{ !cancelled() }}
+ with:
+ name: bazel-test-logs-${{ matrix.os }}
+ path: |
+ bazel-out/*/testlogs/**/*.log
+ bazel-out/*/testlogs/**/*.xml
+ if-no-files-found: ignore
cpp_xlang:
name: C++ Xlang Test
diff --git a/.gitignore b/.gitignore
index 63d109ab7..35b82d6f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,3 +115,4 @@ examples/cpp/cmake_example/build
**/benchmark_*.png
**/results/
benchmarks/**/report/
+ignored/**
\ No newline at end of file
diff --git a/benchmarks/cpp_benchmark/README.md
b/benchmarks/cpp_benchmark/README.md
index b66a3b04f..42c1e4cf0 100644
--- a/benchmarks/cpp_benchmark/README.md
+++ b/benchmarks/cpp_benchmark/README.md
@@ -29,14 +29,14 @@ Note: Protobuf is fetched automatically via CMake
FetchContent, so no manual ins
<img src="../../docs/benchmarks/cpp/throughput.png" width="90%">
</p>
-| Datatype | Operation | Fory TPS | Protobuf TPS | Faster |
-| ------------ | ----------- | ---------- | ------------ | ----------- |
-| Mediacontent | Serialize | 11,319,876 | 1,181,595 | Fory (9.6x) |
-| Mediacontent | Deserialize | 2,729,388 | 835,956 | Fory (3.3x) |
-| Sample | Serialize | 16,899,403 | 10,575,760 | Fory (1.6x) |
-| Sample | Deserialize | 3,079,241 | 1,450,789 | Fory (2.1x) |
-| Struct | Serialize | 43,184,198 | 29,359,454 | Fory (1.5x) |
-| Struct | Deserialize | 54,599,691 | 38,796,674 | Fory (1.4x) |
+| Datatype | Operation | Fory TPS | Protobuf TPS | Faster |
+| ------------ | ----------- | --------- | ------------ | ----------- |
+| Mediacontent | Serialize | 2,254,915 | 504,410 | Fory (4.5x) |
+| Mediacontent | Deserialize | 741,303 | 396,013 | Fory (1.9x) |
+| Sample | Serialize | 4,248,973 | 3,229,102 | Fory (1.3x) |
+| Sample | Deserialize | 935,709 | 715,837 | Fory (1.3x) |
+| Struct | Serialize | 9,143,618 | 5,881,005 | Fory (1.6x) |
+| Struct | Deserialize | 7,746,787 | 6,202,164 | Fory (1.2x) |
## Quick Start
diff --git a/compiler/README.md b/compiler/README.md
index b8fbb77c7..452bce913 100644
--- a/compiler/README.md
+++ b/compiler/README.md
@@ -384,8 +384,10 @@ struct Cat {
std::shared_ptr<Dog> friend;
std::optional<std::string> name;
std::vector<std::string> tags;
+ int32_t scores;
+ int32_t lives;
+ FORY_STRUCT(Cat, friend, name, tags, scores, lives);
};
-FORY_STRUCT(Cat, friend, name, tags, scores, lives);
```
## CLI Reference
diff --git a/compiler/fory_compiler/generators/cpp.py
b/compiler/fory_compiler/generators/cpp.py
index 2580c5abb..49298cb86 100644
--- a/compiler/fory_compiler/generators/cpp.py
+++ b/compiler/fory_compiler/generators/cpp.py
@@ -114,7 +114,6 @@ class CppGenerator(BaseGenerator):
lines = []
includes: Set[str] = set()
enum_macros: List[str] = []
- struct_macros: List[str] = []
union_macros: List[str] = []
field_config_macros: List[str] = []
definition_items = self.get_definition_order()
@@ -173,9 +172,7 @@ class CppGenerator(BaseGenerator):
# Generate top-level unions/messages in dependency order
for kind, item in definition_items:
if kind == "union":
- lines.extend(
- self.generate_union_definition(item, [], struct_macros, "")
- )
+ lines.extend(self.generate_union_definition(item, [], ""))
union_macros.extend(self.generate_union_macros(item, []))
lines.append("")
continue
@@ -183,7 +180,6 @@ class CppGenerator(BaseGenerator):
self.generate_message_definition(
item,
[],
- struct_macros,
enum_macros,
union_macros,
field_config_macros,
@@ -192,10 +188,6 @@ class CppGenerator(BaseGenerator):
)
lines.append("")
- if struct_macros:
- lines.extend(struct_macros)
- lines.append("")
-
if namespace:
lines.append(f"}} // namespace {namespace}")
lines.append("")
@@ -396,7 +388,6 @@ class CppGenerator(BaseGenerator):
self,
message: Message,
parent_stack: List[Message],
- struct_macros: List[str],
enum_macros: List[str],
union_macros: List[str],
field_config_macros: List[str],
@@ -422,7 +413,6 @@ class CppGenerator(BaseGenerator):
self.generate_message_definition(
nested_msg,
lineage,
- struct_macros,
enum_macros,
union_macros,
field_config_macros,
@@ -436,7 +426,6 @@ class CppGenerator(BaseGenerator):
self.generate_union_definition(
nested_union,
lineage,
- struct_macros,
body_indent,
)
)
@@ -470,12 +459,9 @@ class CppGenerator(BaseGenerator):
lines.append(f"{body_indent} return true;")
lines.append(f"{body_indent}}}")
- lines.append(f"{indent}}};")
-
struct_type_name = self.get_qualified_type_name(message.name,
parent_stack)
if message.fields:
field_names = ", ".join(self.to_snake_case(f.name) for f in
message.fields)
- struct_macros.append(f"FORY_STRUCT({struct_type_name},
{field_names});")
field_config_type_name = self.get_field_config_type_and_alias(
message.name, parent_stack
)
@@ -484,8 +470,13 @@ class CppGenerator(BaseGenerator):
message, field_config_type_name[0],
field_config_type_name[1]
)
)
+ lines.append(
+ f"{body_indent}FORY_STRUCT({struct_type_name}, {field_names});"
+ )
else:
- struct_macros.append(f"FORY_STRUCT({struct_type_name});")
+ lines.append(f"{body_indent}FORY_STRUCT({struct_type_name});")
+
+ lines.append(f"{indent}}};")
return lines
@@ -493,7 +484,6 @@ class CppGenerator(BaseGenerator):
self,
union: Union,
parent_stack: List[Message],
- struct_macros: List[str],
indent: str,
) -> List[str]:
"""Generate a C++ union class definition."""
diff --git a/cpp/README.md b/cpp/README.md
index dc74e7919..0680cc3c9 100644
--- a/cpp/README.md
+++ b/cpp/README.md
@@ -23,7 +23,9 @@ The C++ implementation provides high-performance
serialization with compile-time
```cpp
#include "fory/serialization/fory.h"
-// Define your struct with FORY_STRUCT macro
+// Define your class with FORY_STRUCT macro (struct works the same way).
+// Place it after all fields.
+// When used inside a class, it must be placed in a public: section.
struct Person {
std::string name;
int32_t age;
@@ -84,14 +86,16 @@ Apache Fory™ provides automatic serialization of complex
object graphs, preser
```cpp
#include "fory/serialization/fory.h"
-struct Address {
+class Address {
+public:
std::string street;
std::string city;
std::string country;
FORY_STRUCT(Address, street, city, country);
};
-struct Person {
+class Person {
+public:
std::string name;
int32_t age;
Address address;
@@ -116,6 +120,43 @@ auto bytes = fory->serialize(person).value();
auto decoded = fory->deserialize<Person>(bytes).value();
```
+### 1.1 External/Third-Party Types
+
+For third-party types where you cannot modify the class definition, use
+`FORY_STRUCT` at namespace scope. This works for public fields only.
+
+```cpp
+namespace thirdparty {
+struct Foo {
+ int32_t id;
+ std::string name;
+};
+
+FORY_STRUCT(Foo, id, name);
+} // namespace thirdparty
+
+auto fory = apache::fory::ForyBuilder().build();
+fory->register_struct<thirdparty::Foo>(1);
+```
+
+### 1.2 Inherited Fields
+
+To include base-class fields in a derived type, use `FORY_BASE(Base)` inside
+`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields can be
+referenced.
+
+```cpp
+struct Base {
+ int32_t id;
+ FORY_STRUCT(Base, id);
+};
+
+struct Derived : Base {
+ std::string name;
+ FORY_STRUCT(Derived, FORY_BASE(Base), name);
+};
+```
+
### 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.
@@ -230,7 +271,7 @@ struct UserProfile {
std::string email;
std::vector<int32_t> scores;
bool is_active;
- FORY_FIELD_INFO(UserProfile, id, username, email, scores, is_active);
+ FORY_STRUCT(UserProfile, id, username, email, scores, is_active);
};
apache::fory::RowEncoder<UserProfile> encoder;
diff --git a/cpp/fory/encoder/row_encode_trait.h
b/cpp/fory/encoder/row_encode_trait.h
index 134cf9643..b1facd3c3 100644
--- a/cpp/fory/encoder/row_encode_trait.h
+++ b/cpp/fory/encoder/row_encode_trait.h
@@ -192,7 +192,7 @@ private:
template <size_t I> static FieldPtr GetField() {
using FieldType = meta::RemoveMemberPointerCVRefT<
- std::tuple_element_t<I, decltype(FieldInfo::Ptrs)>>;
+ std::tuple_element_t<I, decltype(FieldInfo::Ptrs())>>;
return field(details::StringViewToString(FieldInfo::Names[I]),
RowEncodeTrait<FieldType>::Type());
}
@@ -205,9 +205,9 @@ private:
template <size_t I, typename V>
static void WriteField(V &&visitor, const T &value, RowWriter &writer) {
using FieldType = meta::RemoveMemberPointerCVRefT<
- std::tuple_element_t<I, decltype(FieldInfo::Ptrs)>>;
+ std::tuple_element_t<I, decltype(FieldInfo::Ptrs())>>;
RowEncodeTrait<FieldType>::Write(std::forward<V>(visitor),
- value.*std::get<I>(FieldInfo::Ptrs),
+ value.*std::get<I>(FieldInfo::PtrsRef()),
writer, I);
}
diff --git a/cpp/fory/encoder/row_encode_trait_test.cc
b/cpp/fory/encoder/row_encode_trait_test.cc
index 3d153d2e7..4fd381908 100644
--- a/cpp/fory/encoder/row_encode_trait_test.cc
+++ b/cpp/fory/encoder/row_encode_trait_test.cc
@@ -34,10 +34,9 @@ struct A {
int x;
float y;
bool z;
+ FORY_STRUCT(A, x, y, z);
};
-FORY_FIELD_INFO(A, x, y, z);
-
TEST(RowEncodeTrait, Basic) {
auto field_vector = encoder::RowEncodeTrait<A>::FieldVector();
@@ -67,10 +66,9 @@ TEST(RowEncodeTrait, Basic) {
struct B {
int num;
std::string str;
+ FORY_STRUCT(B, num, str);
};
-FORY_FIELD_INFO(B, num, str);
-
TEST(RowEncodeTrait, String) {
RowWriter writer(encoder::RowEncodeTrait<B>::Schema());
writer.Reset();
@@ -89,10 +87,9 @@ struct C {
const int a;
volatile float b;
bool c;
+ FORY_STRUCT(C, a, b, c);
};
-FORY_FIELD_INFO(C, a, b, c);
-
TEST(RowEncodeTrait, Const) {
RowWriter writer(encoder::RowEncodeTrait<C>::Schema());
writer.Reset();
@@ -110,10 +107,9 @@ struct D {
int x;
A y;
B z;
+ FORY_STRUCT(D, x, y, z);
};
-FORY_FIELD_INFO(D, x, y, z);
-
TEST(RowEncodeTrait, NestedStruct) {
RowWriter writer(encoder::RowEncodeTrait<D>::Schema());
std::vector<std::unique_ptr<RowWriter>> children;
@@ -202,10 +198,9 @@ TEST(RowEncodeTrait, StructInArray) {
struct E {
int a;
std::vector<int> b;
+ FORY_STRUCT(E, a, b);
};
-FORY_FIELD_INFO(E, a, b);
-
TEST(RowEncodeTrait, ArrayInStruct) {
E e{233, {10, 20, 30}};
@@ -256,10 +251,9 @@ struct F {
bool a;
std::optional<int> b;
int c;
+ FORY_STRUCT(F, a, b, c);
};
-FORY_FIELD_INFO(F, a, b, c);
-
TEST(RowEncodeTrait, Optional) {
F x{false, 233, 111}, y{true, std::nullopt, 222};
@@ -301,10 +295,9 @@ TEST(RowEncodeTrait, Optional) {
struct G {
std::map<int, std::map<int, int>> a;
std::map<std::string, A> b;
+ FORY_STRUCT(G, a, b);
};
-FORY_FIELD_INFO(G, a, b);
-
TEST(RowEncodeTrait, Map) {
G v{{{1, {{3, 4}, {5, 6}}}, {2, {{7, 8}, {9, 10}, {11, 12}}}},
{{"a", A{1, 1.1, true}}, {"b", A{2, 3.3, false}}}};
diff --git a/cpp/fory/encoder/row_encoder_test.cc
b/cpp/fory/encoder/row_encoder_test.cc
index 93355d2ec..a410ce33a 100644
--- a/cpp/fory/encoder/row_encoder_test.cc
+++ b/cpp/fory/encoder/row_encoder_test.cc
@@ -32,16 +32,48 @@ namespace test2 {
struct A {
float a;
std::string b;
+ FORY_STRUCT(A, a, b);
};
-FORY_FIELD_INFO(A, a, b);
-
struct B {
int x;
A y;
+ FORY_STRUCT(B, x, y);
+};
+
+namespace external_row {
+
+struct ExternalRow {
+ int32_t id;
+ std::string name;
+};
+
+FORY_STRUCT(ExternalRow, id, name);
+
+struct ExternalRowEmpty {};
+
+FORY_STRUCT(ExternalRowEmpty);
+
+} // namespace external_row
+
+namespace nested_row {
+namespace inner {
+
+struct InClassRow {
+ int32_t id;
+ std::string name;
+ FORY_STRUCT(InClassRow, id, name);
};
-FORY_FIELD_INFO(B, x, y);
+struct OutClassRow {
+ int32_t id;
+ std::string name;
+};
+
+FORY_STRUCT(OutClassRow, id, name);
+
+} // namespace inner
+} // namespace nested_row
TEST(RowEncoder, Simple) {
B v{233, {1.23, "hello"}};
@@ -66,13 +98,56 @@ TEST(RowEncoder, Simple) {
ASSERT_FLOAT_EQ(y_row->GetFloat(0), 1.23);
}
+TEST(RowEncoder, ExternalStruct) {
+ external_row::ExternalRow v{7, "external"};
+ encoder::RowEncoder<external_row::ExternalRow> enc;
+
+ auto &schema = enc.GetSchema();
+ ASSERT_EQ(schema.field_names(), (std::vector<std::string>{"id", "name"}));
+
+ enc.Encode(v);
+ auto row = enc.GetWriter().ToRow();
+ ASSERT_EQ(row->GetInt32(0), 7);
+ ASSERT_EQ(row->GetString(1), "external");
+}
+
+TEST(RowEncoder, ExternalEmptyStruct) {
+ external_row::ExternalRowEmpty v{};
+ encoder::RowEncoder<external_row::ExternalRowEmpty> enc;
+
+ auto &schema = enc.GetSchema();
+ ASSERT_TRUE(schema.field_names().empty());
+
+ enc.Encode(v);
+ auto row = enc.GetWriter().ToRow();
+ ASSERT_EQ(row->num_fields(), 0);
+}
+
+TEST(RowEncoder, NestedNamespaceStructs) {
+ nested_row::inner::InClassRow in{11, "in"};
+ nested_row::inner::OutClassRow out{22, "out"};
+
+ encoder::RowEncoder<nested_row::inner::InClassRow> in_enc;
+ encoder::RowEncoder<nested_row::inner::OutClassRow> out_enc;
+
+ in_enc.Encode(in);
+ out_enc.Encode(out);
+
+ auto in_row = in_enc.GetWriter().ToRow();
+ auto out_row = out_enc.GetWriter().ToRow();
+
+ ASSERT_EQ(in_row->GetInt32(0), 11);
+ ASSERT_EQ(in_row->GetString(1), "in");
+ ASSERT_EQ(out_row->GetInt32(0), 22);
+ ASSERT_EQ(out_row->GetString(1), "out");
+}
+
struct C {
std::vector<A> x;
bool y;
+ FORY_STRUCT(C, x, y);
};
-FORY_FIELD_INFO(C, x, y);
-
TEST(RowEncoder, SimpleArray) {
std::vector<C> v{C{{{1, "a"}, {2, "b"}}, false},
C{{{1.1, "x"}, {2.2, "y"}, {3.3, "z"}}, true}};
diff --git a/cpp/fory/meta/field_info.h b/cpp/fory/meta/field_info.h
index ec7dcd3a9..de2fa3b09 100644
--- a/cpp/fory/meta/field_info.h
+++ b/cpp/fory/meta/field_info.h
@@ -31,22 +31,54 @@ namespace fory {
namespace meta {
-// decltype(ForyFieldInfo<T>(v)) records field meta information for type T
-// it includes:
-// - number of fields: typed size_t
-// - field names: typed `std::string_view`
-// - field member points: typed `decltype(a) T::*` for any member `T::a`
-template <typename T> constexpr auto ForyFieldInfo(const T &) noexcept {
- static_assert(AlwaysFalse<T>,
- "FORY_FIELD_INFO for type T is expected but not defined");
-}
+template <typename T> struct Identity {
+ using Type = T;
+};
namespace details {
+template <typename T>
+using MemberStructInfo =
+ decltype(T::ForyStructInfo(std::declval<Identity<T>>()));
+
+template <typename T, typename = void>
+struct HasMemberStructInfo : std::false_type {};
+
+template <typename T>
+struct HasMemberStructInfo<T, std::void_t<MemberStructInfo<T>>>
+ : std::true_type {};
+
+template <typename T>
+using AdlStructInfo = decltype(ForyStructInfo(std::declval<Identity<T>>()));
+
+template <typename T, typename = void>
+struct HasAdlStructInfo : std::false_type {};
+
+template <typename T>
+struct HasAdlStructInfo<T, std::void_t<AdlStructInfo<T>>> : std::true_type {};
+
+template <typename T> struct TupleWrapper {
+ T value;
+};
+
+template <typename T> constexpr TupleWrapper<T> WrapTuple(const T &value) {
+ return {value};
+}
+
+template <typename T> constexpr const T &UnwrapTuple(const T &value) {
+ return value;
+}
+
+template <typename T>
+constexpr const T &UnwrapTuple(const TupleWrapper<T> &value) {
+ return value.value;
+}
+
// it must be able to be executed in compile-time
template <typename FieldInfo, size_t... I>
constexpr bool IsValidFieldInfoImpl(std::index_sequence<I...>) {
- return IsUnique<std::get<I>(FieldInfo::Ptrs)...>::value;
+ constexpr auto ptrs = FieldInfo::Ptrs();
+ return IsUnique<std::get<I>(ptrs)...>::value;
}
} // namespace details
@@ -56,41 +88,267 @@ template <typename FieldInfo> constexpr bool
IsValidFieldInfo() {
std::make_index_sequence<FieldInfo::Size>{});
}
+template <typename T>
+struct HasForyStructInfo
+ : std::bool_constant<details::HasMemberStructInfo<T>::value ||
+ details::HasAdlStructInfo<T>::value> {};
+
+// decltype(ForyFieldInfo<T>(v)) records field meta information for type T
+// it includes:
+// - number of fields: typed size_t
+// - field names: typed `std::string_view`
+// - field member points: typed `decltype(a) T::*` for any member `T::a`
+template <typename T>
+constexpr auto ForyFieldInfo([[maybe_unused]] const T &value) noexcept {
+ if constexpr (details::HasMemberStructInfo<T>::value) {
+ using FieldInfo = decltype(T::ForyStructInfo(Identity<T>{}));
+ static_assert(IsValidFieldInfo<FieldInfo>(),
+ "duplicated fields in FORY_STRUCT arguments are detected");
+ return T::ForyStructInfo(Identity<T>{});
+ } else if constexpr (details::HasAdlStructInfo<T>::value) {
+ using FieldInfo = decltype(ForyStructInfo(Identity<T>{}));
+ static_assert(IsValidFieldInfo<FieldInfo>(),
+ "duplicated fields in FORY_STRUCT arguments are detected");
+ return ForyStructInfo(Identity<T>{});
+ } else {
+ static_assert(AlwaysFalse<T>,
+ "FORY_STRUCT for type T is expected but not defined");
+ }
+}
+
+constexpr std::array<std::string_view, 0> ConcatArrays() { return {}; }
+
+template <size_t N>
+constexpr std::array<std::string_view, N>
+ConcatArrays(const std::array<std::string_view, N> &value) {
+ return value;
+}
+
+template <size_t N, size_t M>
+constexpr std::array<std::string_view, N + M>
+ConcatArrays(const std::array<std::string_view, N> &left,
+ const std::array<std::string_view, M> &right) {
+ std::array<std::string_view, N + M> out{};
+ for (size_t i = 0; i < N; ++i) {
+ out[i] = left[i];
+ }
+ for (size_t i = 0; i < M; ++i) {
+ out[N + i] = right[i];
+ }
+ return out;
+}
+
+template <size_t N, size_t M, typename... Rest>
+constexpr auto ConcatArrays(const std::array<std::string_view, N> &left,
+ const std::array<std::string_view, M> &right,
+ const Rest &...rest) {
+ return ConcatArrays(ConcatArrays(left, right), rest...);
+}
+
+constexpr std::tuple<> ConcatTuples() { return {}; }
+
+template <typename Tuple> constexpr Tuple ConcatTuples(const Tuple &tuple) {
+ return tuple;
+}
+
+template <typename Tuple1, typename Tuple2, size_t... I, size_t... J>
+constexpr auto ConcatTwoTuplesImpl(const Tuple1 &left, const Tuple2 &right,
+ std::index_sequence<I...>,
+ std::index_sequence<J...>) {
+ return std::tuple{std::get<I>(left)..., std::get<J>(right)...};
+}
+
+template <typename Tuple1, typename Tuple2>
+constexpr auto ConcatTuples(const Tuple1 &left, const Tuple2 &right) {
+ return ConcatTwoTuplesImpl(
+ left, right, std::make_index_sequence<std::tuple_size_v<Tuple1>>{},
+ std::make_index_sequence<std::tuple_size_v<Tuple2>>{});
+}
+
+template <typename Tuple1, typename Tuple2, typename... Rest>
+constexpr auto ConcatTuples(const Tuple1 &left, const Tuple2 &right,
+ const Rest &...rest) {
+ return ConcatTuples(ConcatTuples(left, right), rest...);
+}
+
+template <typename Tuple, size_t... I>
+constexpr auto ConcatArraysFromTupleImpl(const Tuple &tuple,
+ std::index_sequence<I...>) {
+ return ConcatArrays(std::get<I>(tuple)...);
+}
+
+template <typename Tuple>
+constexpr auto ConcatArraysFromTuple(const Tuple &tuple) {
+ return ConcatArraysFromTupleImpl(
+ tuple, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
+}
+
+template <typename Tuple, size_t... I>
+constexpr auto ConcatTuplesFromTupleImpl(const Tuple &tuple,
+ std::index_sequence<I...>) {
+ return ConcatTuples(details::UnwrapTuple(std::get<I>(tuple))...);
+}
+
+template <typename Tuple>
+constexpr auto ConcatTuplesFromTuple(const Tuple &tuple) {
+ return ConcatTuplesFromTupleImpl(
+ tuple, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
+}
+
} // namespace meta
} // namespace fory
-#define FORY_FIELD_INFO_NAMES_FUNC(field) #field,
-#define FORY_FIELD_INFO_PTRS_FUNC(type, field) &type::field,
+#define FORY_BASE(type) (FORY_BASE_TAG, type)
+
+#define FORY_PP_IS_BASE_TAG(x)
\
+ FORY_PP_CHECK(FORY_PP_CONCAT(FORY_PP_IS_BASE_TAG_PROBE_, x))
+#define FORY_PP_IS_BASE_TAG_PROBE_FORY_BASE_TAG FORY_PP_PROBE()
+
+#define FORY_PP_IS_BASE(arg) FORY_PP_IS_BASE_IMPL(FORY_PP_IS_PAREN(arg), arg)
+#define FORY_PP_IS_BASE_IMPL(is_paren, arg)
\
+ FORY_PP_CONCAT(FORY_PP_IS_BASE_IMPL_, is_paren)(arg)
+#define FORY_PP_IS_BASE_IMPL_0(arg) 0
+#define FORY_PP_IS_BASE_IMPL_1(arg)
\
+ FORY_PP_IS_BASE_TAG(FORY_PP_TUPLE_FIRST(arg))
-// here we define function overloads in the current namespace rather than
-// template specialization of classes since specialization of template in
-// different namespace is hard
-// NOTE: for performing ADL (argument-dependent lookup),
-// `FORY_FIELD_INFO(T, ...)` must be defined in the same namespace as `T`
-#define FORY_FIELD_INFO(type, ...)
\
+#define FORY_BASE_TYPE(arg) FORY_PP_TUPLE_SECOND(arg)
+
+#define FORY_FIELD_INFO_NAMES_FIELD(field) #field,
+#define FORY_FIELD_INFO_NAMES_FUNC(arg)
\
+ FORY_PP_IF(FORY_PP_IS_BASE(arg))
\
+ (FORY_PP_EMPTY(), FORY_FIELD_INFO_NAMES_FIELD(arg))
+
+#define FORY_FIELD_INFO_PTRS_FIELD(type, field) &type::field,
+#define FORY_FIELD_INFO_PTRS_FUNC(type, arg)
\
+ FORY_PP_IF(FORY_PP_IS_BASE(arg))
\
+ (FORY_PP_EMPTY(), FORY_FIELD_INFO_PTRS_FIELD(type, arg))
+
+#define FORY_BASE_NAMES_ARG(arg)
\
+ FORY_PP_IF(FORY_PP_IS_BASE(arg))
\
+ (FORY_BASE_NAMES_ARG_IMPL(arg), FORY_PP_EMPTY())
+#define FORY_BASE_NAMES_ARG_IMPL(arg)
\
+ decltype(::fory::meta::ForyFieldInfo(
\
+ std::declval<FORY_BASE_TYPE(arg)>()))::Names,
+
+#define FORY_BASE_PTRS_ARG(arg)
\
+ FORY_PP_IF(FORY_PP_IS_BASE(arg))
\
+ (FORY_BASE_PTRS_ARG_IMPL(arg), FORY_PP_EMPTY())
+#define FORY_BASE_PTRS_ARG_IMPL(arg)
\
+ fory::meta::details::WrapTuple(decltype(::fory::meta::ForyFieldInfo(
\
+ std::declval<FORY_BASE_TYPE(arg)>()))::Ptrs()),
+
+#define FORY_BASE_SIZE_ADD(arg)
\
+ FORY_PP_IF(FORY_PP_IS_BASE(arg))
\
+ (+decltype(::fory::meta::ForyFieldInfo(
\
+ std::declval<FORY_BASE_TYPE(arg)>()))::Size,
\
+ FORY_PP_EMPTY())
+
+#define FORY_FIELD_SIZE_ADD(arg)
\
+ FORY_PP_IF(FORY_PP_IS_BASE(arg))(FORY_PP_EMPTY(), +1)
+
+// NOTE: FORY_STRUCT can be used inside the class/struct definition or at
+// namespace scope. The macro defines constexpr functions which are detected
+// via member lookup (in-class) or ADL (namespace scope).
+// MSVC (VS 2022 17.11, 19.41) fixes in-class pointer-to-member constexpr
+// issues; keep evaluation inside `Ptrs` function instead of field for older
+// toolsets.
+#define FORY_STRUCT_FIELDS(type, unique_id, ...)
\
static_assert(std::is_class_v<type>, "it must be a class type");
\
- template <typename> struct ForyFieldInfoImpl;
\
- template <> struct ForyFieldInfoImpl<type> {
\
- static inline constexpr size_t Size = FORY_PP_NARG(__VA_ARGS__);
\
+ struct FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id) {
\
+ static inline constexpr size_t BaseSize =
\
+ 0 FORY_PP_FOREACH(FORY_BASE_SIZE_ADD, __VA_ARGS__);
\
+ static inline constexpr size_t FieldSize =
\
+ 0 FORY_PP_FOREACH(FORY_FIELD_SIZE_ADD, __VA_ARGS__);
\
+ static inline constexpr size_t Size = BaseSize + FieldSize;
\
static inline constexpr std::string_view Name = #type;
\
- static inline constexpr std::array<std::string_view, Size> Names = {
\
- FORY_PP_FOREACH(FORY_FIELD_INFO_NAMES_FUNC, __VA_ARGS__)};
\
- static inline constexpr auto Ptrs = std::tuple{
\
- FORY_PP_FOREACH_1(FORY_FIELD_INFO_PTRS_FUNC, type, __VA_ARGS__)};
\
+ static inline constexpr auto BaseNames =
\
+ fory::meta::ConcatArraysFromTuple(
\
+ std::tuple{FORY_PP_FOREACH(FORY_BASE_NAMES_ARG, __VA_ARGS__)});
\
+ static inline constexpr std::array<std::string_view, FieldSize>
\
+ FieldNames = {
\
+ FORY_PP_FOREACH(FORY_FIELD_INFO_NAMES_FUNC, __VA_ARGS__)};
\
+ static inline constexpr auto Names =
\
+ fory::meta::ConcatArrays(BaseNames, FieldNames);
\
+ using BasePtrsType = decltype(fory::meta::ConcatTuplesFromTuple(
\
+ std::tuple{FORY_PP_FOREACH(FORY_BASE_PTRS_ARG, __VA_ARGS__)}));
\
+ static constexpr BasePtrsType BasePtrs() {
\
+ return fory::meta::ConcatTuplesFromTuple(
\
+ std::tuple{FORY_PP_FOREACH(FORY_BASE_PTRS_ARG, __VA_ARGS__)});
\
+ }
\
+ using FieldPtrsType = decltype(std::tuple{
\
+ FORY_PP_FOREACH_1(FORY_FIELD_INFO_PTRS_FUNC, type, __VA_ARGS__)});
\
+ static constexpr FieldPtrsType FieldPtrs() {
\
+ return std::tuple{
\
+ FORY_PP_FOREACH_1(FORY_FIELD_INFO_PTRS_FUNC, type, __VA_ARGS__)};
\
+ }
\
+ using PtrsType = decltype(fory::meta::ConcatTuples(
\
+ std::declval<BasePtrsType>(), std::declval<FieldPtrsType>()));
\
+ static constexpr PtrsType Ptrs() {
\
+ return fory::meta::ConcatTuples(BasePtrs(), FieldPtrs());
\
+ }
\
+ static const PtrsType &PtrsRef() {
\
+ static const PtrsType value = Ptrs();
\
+ return value;
\
+ }
\
};
\
+ static_assert(FORY_PP_CONCAT(ForyFieldInfoDescriptor_,
\
+ unique_id)::Name.data() != nullptr,
\
+ "ForyFieldInfoDescriptor name must be available");
\
static_assert(
\
- fory::meta::IsValidFieldInfo<ForyFieldInfoImpl<type>>(),
\
- "duplicated fields in FORY_FIELD_INFO arguments are detected");
\
- static_assert(ForyFieldInfoImpl<type>::Name.data() != nullptr,
\
- "ForyFieldInfoImpl name must be available");
\
- static_assert(ForyFieldInfoImpl<type>::Names.size() ==
\
- ForyFieldInfoImpl<type>::Size,
\
- "ForyFieldInfoImpl names size mismatch");
\
- inline constexpr auto ForyFieldInfo(const type &) noexcept {
\
- return ForyFieldInfoImpl<type>{};
\
+ FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id)::Names.size() ==
\
+ FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id)::Size,
\
+ "ForyFieldInfoDescriptor names size mismatch");
\
+ [[maybe_unused]] inline static constexpr auto ForyStructInfo(
\
+ const ::fory::meta::Identity<type> &) noexcept {
\
+ return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){};
\
}
\
+ [[maybe_unused]] inline static constexpr std::true_type ForyStructMarker(
\
+ const ::fory::meta::Identity<type> &) noexcept {
\
+ return {};
\
+ }
+
+#define FORY_STRUCT_DETAIL_EMPTY(type, unique_id)
\
+ static_assert(std::is_class_v<type>, "it must be a class type");
\
+ struct FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id) {
\
+ static inline constexpr size_t Size = 0;
\
+ static inline constexpr std::string_view Name = #type;
\
+ static inline constexpr std::array<std::string_view, Size> Names = {};
\
+ using PtrsType = decltype(std::tuple{});
\
+ static constexpr PtrsType Ptrs() { return {}; }
\
+ static const PtrsType &PtrsRef() {
\
+ static const PtrsType value = Ptrs();
\
+ return value;
\
+ }
\
+ };
\
+ static_assert(FORY_PP_CONCAT(ForyFieldInfoDescriptor_,
\
+ unique_id)::Name.data() != nullptr,
\
+ "ForyFieldInfoDescriptor name must be available");
\
static_assert(
\
- static_cast<ForyFieldInfoImpl<type> (*)(const type &) noexcept>(
\
- &ForyFieldInfo) != nullptr,
\
- "ForyFieldInfo must be declared");
+ FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id)::Names.size() ==
\
+ FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id)::Size,
\
+ "ForyFieldInfoDescriptor names size mismatch");
\
+ [[maybe_unused]] inline static constexpr auto ForyStructInfo(
\
+ const ::fory::meta::Identity<type> &) noexcept {
\
+ return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){};
\
+ }
\
+ [[maybe_unused]] inline static constexpr std::true_type ForyStructMarker(
\
+ const ::fory::meta::Identity<type> &) noexcept {
\
+ return {};
\
+ }
+
+#define FORY_STRUCT_WITH_FIELDS(type, unique_id, ...)
\
+ FORY_STRUCT_FIELDS(type, unique_id, __VA_ARGS__)
+
+#define FORY_STRUCT_EMPTY(type, unique_id)
\
+ FORY_STRUCT_DETAIL_EMPTY(type, unique_id)
+
+#define FORY_STRUCT_1(type, unique_id, ...) FORY_STRUCT_EMPTY(type, unique_id)
+#define FORY_STRUCT_0(type, unique_id, ...)
\
+ FORY_STRUCT_WITH_FIELDS(type, unique_id, __VA_ARGS__)
+
+#define FORY_STRUCT_IMPL(type, unique_id, ...)
\
+ FORY_PP_CONCAT(FORY_STRUCT_, FORY_PP_IS_EMPTY(__VA_ARGS__))
\
+ (type, unique_id, __VA_ARGS__)
+
+#define FORY_STRUCT(type, ...) FORY_STRUCT_IMPL(type, __LINE__, __VA_ARGS__)
diff --git a/cpp/fory/meta/field_info_test.cc b/cpp/fory/meta/field_info_test.cc
index 6cb79a9b2..f07bf0067 100644
--- a/cpp/fory/meta/field_info_test.cc
+++ b/cpp/fory/meta/field_info_test.cc
@@ -29,13 +29,12 @@ struct A {
int x;
float y;
bool z;
+ FORY_STRUCT(A, x, y, z);
};
-FORY_FIELD_INFO(A, x, y, z);
-
TEST(FieldInfo, Simple) {
A a;
- constexpr auto info = ForyFieldInfo(a);
+ constexpr auto info = meta::ForyFieldInfo(a);
static_assert(info.Size == 3);
@@ -45,21 +44,20 @@ TEST(FieldInfo, Simple) {
static_assert(info.Names[1] == "y");
static_assert(info.Names[2] == "z");
- static_assert(std::get<0>(info.Ptrs) == &A::x);
- static_assert(std::get<1>(info.Ptrs) == &A::y);
- static_assert(std::get<2>(info.Ptrs) == &A::z);
+ static_assert(std::get<0>(decltype(info)::Ptrs()) == &A::x);
+ static_assert(std::get<1>(decltype(info)::Ptrs()) == &A::y);
+ static_assert(std::get<2>(decltype(info)::Ptrs()) == &A::z);
}
struct B {
A a;
int hidden;
+ FORY_STRUCT(B, a);
};
-FORY_FIELD_INFO(B, a);
-
TEST(FieldInfo, Hidden) {
B b;
- constexpr auto info = ForyFieldInfo(b);
+ constexpr auto info = meta::ForyFieldInfo(b);
static_assert(info.Size == 1);
@@ -67,7 +65,7 @@ TEST(FieldInfo, Hidden) {
static_assert(info.Names[0] == "a");
- static_assert(std::get<0>(info.Ptrs) == &B::a);
+ static_assert(std::get<0>(decltype(info)::Ptrs()) == &B::a);
}
} // namespace test
diff --git a/cpp/fory/meta/field_test.cc b/cpp/fory/meta/field_test.cc
index 11379cfb2..f3976fd05 100644
--- a/cpp/fory/meta/field_test.cc
+++ b/cpp/fory/meta/field_test.cc
@@ -240,10 +240,9 @@ struct Person {
field<std::optional<std::string>, 2> nickname;
field<std::shared_ptr<Person>, 3, ref> parent;
field<std::shared_ptr<Person>, 4, nullable> guardian;
+ FORY_STRUCT(Person, name, age, nickname, parent, guardian);
};
-FORY_FIELD_INFO(Person, name, age, nickname, parent, guardian);
-
TEST(FieldStruct, BasicUsage) {
Person p;
p.name = "Alice";
@@ -262,7 +261,7 @@ TEST(FieldStruct, BasicUsage) {
TEST(FieldStruct, FieldInfo) {
Person p;
- constexpr auto info = ForyFieldInfo(p);
+ constexpr auto info = meta::ForyFieldInfo(p);
static_assert(info.Size == 5);
static_assert(info.Name == "Person");
@@ -293,6 +292,8 @@ struct Document {
std::shared_ptr<Document> reviewer;
std::shared_ptr<Document> parent;
std::unique_ptr<std::string> metadata;
+ FORY_STRUCT(Document, title, version, description, author, reviewer, parent,
+ metadata);
};
// Test struct with nullable + ref combined
@@ -300,18 +301,18 @@ struct Node {
std::string name;
std::shared_ptr<Node> left;
std::shared_ptr<Node> right;
+ FORY_STRUCT(Node, name, left, right);
};
// Test with single field
struct SingleField {
int32_t value;
+ FORY_STRUCT(SingleField, value);
};
} // namespace field_tags_test
-// FORY_FIELD_INFO and FORY_FIELD_TAGS must be in global namespace
-FORY_FIELD_INFO(field_tags_test::Document, title, version, description, author,
- reviewer, parent, metadata);
+// FORY_FIELD_TAGS must be in global namespace
// Define field tags separately (non-invasive)
FORY_FIELD_TAGS(field_tags_test::Document, (title, 0), // string: non-nullable
@@ -322,12 +323,9 @@ FORY_FIELD_TAGS(field_tags_test::Document, (title, 0), //
string: non-nullable
(parent, 5, ref), // shared_ptr: non-nullable, with ref
(metadata, 6, nullable)); // unique_ptr: nullable
-FORY_FIELD_INFO(field_tags_test::Node, name, left, right);
-
FORY_FIELD_TAGS(field_tags_test::Node, (name, 0), (left, 1, nullable, ref),
(right, 2, nullable, ref));
-FORY_FIELD_INFO(field_tags_test::SingleField, value);
FORY_FIELD_TAGS(field_tags_test::SingleField, (value, 0));
namespace fory {
diff --git a/cpp/fory/meta/preprocessor.h b/cpp/fory/meta/preprocessor.h
index 771365f11..79f02eb14 100644
--- a/cpp/fory/meta/preprocessor.h
+++ b/cpp/fory/meta/preprocessor.h
@@ -63,6 +63,15 @@
#define FORY_PP_TRIGGER_PARENTHESIS_(...) ,
+#define FORY_PP_EMPTY()
+
+#define FORY_PP_PROBE() ~, 1
+#define FORY_PP_CHECK_N(x, n, ...) n
+#define FORY_PP_CHECK(...) FORY_PP_CHECK_N(__VA_ARGS__, 0)
+
+#define FORY_PP_IS_PAREN(x) FORY_PP_CHECK(FORY_PP_IS_PAREN_PROBE x)
+#define FORY_PP_IS_PAREN_PROBE(...) FORY_PP_PROBE()
+
#define FORY_PP_IS_EMPTY(...)
\
FORY_PP_IS_EMPTY_I(
\
FORY_PP_HAS_COMMA(__VA_ARGS__),
\
@@ -80,6 +89,15 @@
#define FORY_PP_NOT(x) FORY_PP_CONCAT(FORY_PP_NOT_, x)
#define FORY_PP_HAS_ARGS(...) FORY_PP_NOT(FORY_PP_IS_EMPTY(__VA_ARGS__))
+#define FORY_PP_IF(c) FORY_PP_CONCAT(FORY_PP_IF_, c)
+#define FORY_PP_IF_1(t, f) t
+#define FORY_PP_IF_0(t, f) f
+
+#define FORY_PP_TUPLE_FIRST(tuple) FORY_PP_TUPLE_FIRST_IMPL tuple
+#define FORY_PP_TUPLE_FIRST_IMPL(a, ...) a
+#define FORY_PP_TUPLE_SECOND(tuple) FORY_PP_TUPLE_SECOND_IMPL tuple
+#define FORY_PP_TUPLE_SECOND_IMPL(a, b, ...) b
+
#define FORY_PP_INVOKE(X, ...) X(__VA_ARGS__)
// FORY_PP_FOREACH(X, a, b, c) -> X(a) X(b) X(c)
diff --git a/cpp/fory/row/schema.h b/cpp/fory/row/schema.h
index f6ac98646..05e72a8d0 100644
--- a/cpp/fory/row/schema.h
+++ b/cpp/fory/row/schema.h
@@ -89,7 +89,7 @@ public:
virtual int num_fields() const { return 0; }
- virtual FieldPtr field(int i) const { return nullptr; }
+ virtual FieldPtr field(int /*i*/) const { return nullptr; }
virtual std::vector<FieldPtr> fields() const { return {}; }
diff --git a/cpp/fory/serialization/collection_serializer_test.cc
b/cpp/fory/serialization/collection_serializer_test.cc
index e8dddf5f4..d66451079 100644
--- a/cpp/fory/serialization/collection_serializer_test.cc
+++ b/cpp/fory/serialization/collection_serializer_test.cc
@@ -39,31 +39,31 @@ struct Animal {
virtual ~Animal() = default;
virtual std::string speak() const = 0;
int32_t age = 0;
+ FORY_STRUCT(Animal, age);
};
-FORY_STRUCT(Animal, age);
struct Dog : Animal {
std::string speak() const override { return "Woof"; }
std::string name;
+ FORY_STRUCT(Dog, FORY_BASE(Animal), name);
};
-FORY_STRUCT(Dog, age, name);
struct Cat : Animal {
std::string speak() const override { return "Meow"; }
int32_t lives = 9;
+ FORY_STRUCT(Cat, FORY_BASE(Animal), lives);
};
-FORY_STRUCT(Cat, age, lives);
// Holder structs for testing collections as struct fields
struct VectorPolymorphicHolder {
std::vector<std::shared_ptr<Animal>> animals;
+ FORY_STRUCT(VectorPolymorphicHolder, animals);
};
-FORY_STRUCT(VectorPolymorphicHolder, animals);
struct VectorHomogeneousHolder {
std::vector<std::shared_ptr<Dog>> dogs;
+ FORY_STRUCT(VectorHomogeneousHolder, dogs);
};
-FORY_STRUCT(VectorHomogeneousHolder, dogs);
namespace {
@@ -285,13 +285,13 @@ TEST(CollectionSerializerTest, VectorEmpty) {
struct VectorStringHolder {
std::vector<std::string> strings;
+ FORY_STRUCT(VectorStringHolder, strings);
};
-FORY_STRUCT(VectorStringHolder, strings);
struct VectorIntHolder {
std::vector<int32_t> numbers;
+ FORY_STRUCT(VectorIntHolder, numbers);
};
-FORY_STRUCT(VectorIntHolder, numbers);
TEST(CollectionSerializerTest, VectorStringRoundTrip) {
auto fory = Fory::builder().xlang(true).build();
@@ -342,8 +342,8 @@ TEST(CollectionSerializerTest, VectorIntRoundTrip) {
struct VectorOptionalHolder {
std::vector<std::optional<std::string>> values;
+ FORY_STRUCT(VectorOptionalHolder, values);
};
-FORY_STRUCT(VectorOptionalHolder, values);
TEST(CollectionSerializerTest, VectorOptionalWithNulls) {
auto fory = Fory::builder().xlang(true).build();
@@ -377,13 +377,13 @@ TEST(CollectionSerializerTest, VectorOptionalWithNulls) {
struct ListStringHolder {
std::list<std::string> strings;
+ FORY_STRUCT(ListStringHolder, strings);
};
-FORY_STRUCT(ListStringHolder, strings);
struct ListIntHolder {
std::list<int32_t> numbers;
+ FORY_STRUCT(ListIntHolder, numbers);
};
-FORY_STRUCT(ListIntHolder, numbers);
TEST(CollectionSerializerTest, ListStringRoundTrip) {
auto fory = Fory::builder().xlang(true).build();
@@ -459,13 +459,13 @@ TEST(CollectionSerializerTest, ListEmptyRoundTrip) {
struct DequeStringHolder {
std::deque<std::string> strings;
+ FORY_STRUCT(DequeStringHolder, strings);
};
-FORY_STRUCT(DequeStringHolder, strings);
struct DequeIntHolder {
std::deque<int32_t> numbers;
+ FORY_STRUCT(DequeIntHolder, numbers);
};
-FORY_STRUCT(DequeIntHolder, numbers);
TEST(CollectionSerializerTest, DequeStringRoundTrip) {
auto fory = Fory::builder().xlang(true).build();
@@ -539,13 +539,13 @@ TEST(CollectionSerializerTest, DequeEmptyRoundTrip) {
struct ForwardListStringHolder {
std::forward_list<std::string> strings;
+ FORY_STRUCT(ForwardListStringHolder, strings);
};
-FORY_STRUCT(ForwardListStringHolder, strings);
struct ForwardListIntHolder {
std::forward_list<int32_t> numbers;
+ FORY_STRUCT(ForwardListIntHolder, numbers);
};
-FORY_STRUCT(ForwardListIntHolder, numbers);
TEST(CollectionSerializerTest, ForwardListStringRoundTrip) {
auto fory = Fory::builder().xlang(true).build();
diff --git a/cpp/fory/serialization/field_serializer_test.cc
b/cpp/fory/serialization/field_serializer_test.cc
index c40f4cf8f..142381e2f 100644
--- a/cpp/fory/serialization/field_serializer_test.cc
+++ b/cpp/fory/serialization/field_serializer_test.cc
@@ -54,8 +54,8 @@ struct FieldPerson {
score.value == other.score.value &&
active.value == other.active.value;
}
+ FORY_STRUCT(FieldPerson, name, age, score, active);
};
-FORY_STRUCT(FieldPerson, name, age, score, active);
// Struct with optional fields
struct FieldOptionalData {
@@ -68,8 +68,8 @@ struct FieldOptionalData {
optional_age.value == other.optional_age.value &&
optional_email.value == other.optional_email.value;
}
+ FORY_STRUCT(FieldOptionalData, required_name, optional_age, optional_email);
};
-FORY_STRUCT(FieldOptionalData, required_name, optional_age, optional_email);
// Struct with shared_ptr fields (non-nullable by default)
struct FieldSharedPtrHolder {
@@ -87,8 +87,8 @@ struct FieldSharedPtrHolder {
return false;
return true;
}
+ FORY_STRUCT(FieldSharedPtrHolder, value, text);
};
-FORY_STRUCT(FieldSharedPtrHolder, value, text);
// Struct with nullable shared_ptr fields
struct FieldNullableSharedPtr {
@@ -110,15 +110,15 @@ struct FieldNullableSharedPtr {
return false;
return true;
}
+ FORY_STRUCT(FieldNullableSharedPtr, nullable_value, nullable_text);
};
-FORY_STRUCT(FieldNullableSharedPtr, nullable_value, nullable_text);
// Struct with unique_ptr fields
struct FieldUniquePtrHolder {
fory::field<std::unique_ptr<int32_t>, 0> value;
fory::field<std::unique_ptr<int32_t>, 1, fory::nullable> nullable_value;
+ FORY_STRUCT(FieldUniquePtrHolder, value, nullable_value);
};
-FORY_STRUCT(FieldUniquePtrHolder, value, nullable_value);
// Nested struct for reference tracking tests
struct FieldNode {
@@ -128,27 +128,27 @@ struct FieldNode {
bool operator==(const FieldNode &other) const {
return id.value == other.id.value && name.value == other.name.value;
}
+ FORY_STRUCT(FieldNode, id, name);
};
-FORY_STRUCT(FieldNode, id, name);
// Struct with ref tracking for shared_ptr
struct FieldRefTrackingHolder {
fory::field<std::shared_ptr<FieldNode>, 0, fory::ref> first;
fory::field<std::shared_ptr<FieldNode>, 1, fory::ref> second;
+ FORY_STRUCT(FieldRefTrackingHolder, first, second);
};
-FORY_STRUCT(FieldRefTrackingHolder, first, second);
// Struct with nullable + ref
struct FieldNullableRefHolder {
fory::field<std::shared_ptr<FieldNode>, 0, fory::nullable, fory::ref> node;
+ FORY_STRUCT(FieldNullableRefHolder, node);
};
-FORY_STRUCT(FieldNullableRefHolder, node);
// Struct with not_null + ref
struct FieldNotNullRefHolder {
fory::field<std::shared_ptr<FieldNode>, 0, fory::not_null, fory::ref> node;
+ FORY_STRUCT(FieldNotNullRefHolder, node);
};
-FORY_STRUCT(FieldNotNullRefHolder, node);
// Struct with vector of field-wrapped structs
struct FieldVectorHolder {
@@ -157,8 +157,8 @@ struct FieldVectorHolder {
bool operator==(const FieldVectorHolder &other) const {
return nodes.value == other.nodes.value;
}
+ FORY_STRUCT(FieldVectorHolder, nodes);
};
-FORY_STRUCT(FieldVectorHolder, nodes);
// Mixed struct: some fields with fory::field, some without
struct MixedFieldStruct {
@@ -171,8 +171,8 @@ struct MixedFieldStruct {
plain_age == other.plain_age &&
field_score.value == other.field_score.value;
}
+ FORY_STRUCT(MixedFieldStruct, field_name, plain_age, field_score);
};
-FORY_STRUCT(MixedFieldStruct, field_name, plain_age, field_score);
// ============================================================================
// Test Implementation
@@ -669,7 +669,7 @@ TEST(FieldSerializerTest, FieldMetadataCompileTime) {
// ============================================================================
// FORY_FIELD_TAGS Serialization Tests
-// Structs defined in global namespace to allow template specialization
+// FORY_FIELD_TAGS remains namespace-scope, FORY_STRUCT is declared in-class
// ============================================================================
// Simple helper struct for testing FORY_FIELD_TAGS
@@ -680,9 +680,9 @@ struct TagsTestData {
bool operator==(const TagsTestData &other) const {
return content == other.content && value == other.value;
}
+ FORY_STRUCT(TagsTestData, content, value);
};
-FORY_STRUCT(TagsTestData, content, value);
FORY_FIELD_TAGS(TagsTestData, (content, 0), (value, 1));
// Pure C++ struct with FORY_FIELD_TAGS metadata (non-invasive)
@@ -706,10 +706,10 @@ struct TagsTestDocument {
return title == other.title && version == other.version &&
description == other.description && data_eq && opt_data_eq;
}
+ FORY_STRUCT(TagsTestDocument, title, version, description, data,
+ optional_data);
};
-FORY_STRUCT(TagsTestDocument, title, version, description, data,
optional_data);
-
FORY_FIELD_TAGS(TagsTestDocument, (title, 0), // string: non-nullable
(version, 1), // int: non-nullable
(description, 2), // optional: inherently nullable
@@ -724,27 +724,27 @@ struct TagsRefNode {
bool operator==(const TagsRefNode &other) const {
return name == other.name && id == other.id;
}
+ FORY_STRUCT(TagsRefNode, name, id);
};
-FORY_STRUCT(TagsRefNode, name, id);
FORY_FIELD_TAGS(TagsRefNode, (name, 0), (id, 1));
// Struct with ref tracking via FORY_FIELD_TAGS
struct TagsRefHolder {
std::shared_ptr<TagsRefNode> first;
std::shared_ptr<TagsRefNode> second;
+ FORY_STRUCT(TagsRefHolder, first, second);
};
-FORY_STRUCT(TagsRefHolder, first, second);
FORY_FIELD_TAGS(TagsRefHolder, (first, 0, ref), (second, 1, ref));
// Struct with nullable + ref via FORY_FIELD_TAGS
struct TagsNullableRefHolder {
std::shared_ptr<TagsRefNode> required_node;
std::shared_ptr<TagsRefNode> optional_node;
+ FORY_STRUCT(TagsNullableRefHolder, required_node, optional_node);
};
-FORY_STRUCT(TagsNullableRefHolder, required_node, optional_node);
FORY_FIELD_TAGS(TagsNullableRefHolder, (required_node, 0, ref),
(optional_node, 1, nullable, ref));
@@ -753,9 +753,9 @@ struct TagsTreeNode {
std::string value;
std::shared_ptr<TagsTreeNode> left;
std::shared_ptr<TagsTreeNode> right;
+ FORY_STRUCT(TagsTreeNode, value, left, right);
};
-FORY_STRUCT(TagsTreeNode, value, left, right);
FORY_FIELD_TAGS(TagsTreeNode, (value, 0), (left, 1, nullable, ref),
(right, 2, nullable, ref));
diff --git a/cpp/fory/serialization/fory.h b/cpp/fory/serialization/fory.h
index 501df1f07..496a8e62e 100644
--- a/cpp/fory/serialization/fory.h
+++ b/cpp/fory/serialization/fory.h
@@ -221,8 +221,10 @@ public:
///
/// Example:
/// ```cpp
- /// struct MyStruct { int32_t value; };
- /// FORY_STRUCT(MyStruct, value);
+ /// struct MyStruct {
+ /// int32_t value;
+ /// FORY_STRUCT(MyStruct, value);
+ /// };
///
/// fory.register_struct<MyStruct>(1);
/// ```
diff --git a/cpp/fory/serialization/map_serializer_test.cc
b/cpp/fory/serialization/map_serializer_test.cc
index 40caac43a..bf91e939f 100644
--- a/cpp/fory/serialization/map_serializer_test.cc
+++ b/cpp/fory/serialization/map_serializer_test.cc
@@ -342,6 +342,8 @@ struct BaseKey {
bool operator==(const BaseKey &other) const {
return id == other.id && type_name() == other.type_name();
}
+
+ FORY_STRUCT(BaseKey, id);
};
struct DerivedKeyA : public BaseKey {
@@ -352,6 +354,7 @@ struct DerivedKeyA : public BaseKey {
: BaseKey(id), data(std::move(data)) {}
std::string type_name() const override { return "DerivedKeyA"; }
+ FORY_STRUCT(DerivedKeyA, FORY_BASE(BaseKey), data);
};
struct DerivedKeyB : public BaseKey {
@@ -361,6 +364,7 @@ struct DerivedKeyB : public BaseKey {
DerivedKeyB(int32_t id, double value) : BaseKey(id), value(value) {}
std::string type_name() const override { return "DerivedKeyB"; }
+ FORY_STRUCT(DerivedKeyB, FORY_BASE(BaseKey), value);
};
// Base class for polymorphic values
@@ -378,6 +382,8 @@ struct BaseValue {
return name == other.name && get_priority() == other.get_priority() &&
type_name() == other.type_name();
}
+
+ FORY_STRUCT(BaseValue, name);
};
struct DerivedValueX : public BaseValue {
@@ -389,6 +395,7 @@ struct DerivedValueX : public BaseValue {
int32_t get_priority() const override { return priority; }
std::string type_name() const override { return "DerivedValueX"; }
+ FORY_STRUCT(DerivedValueX, FORY_BASE(BaseValue), priority);
};
struct DerivedValueY : public BaseValue {
@@ -402,15 +409,9 @@ struct DerivedValueY : public BaseValue {
int32_t get_priority() const override { return priority; }
std::string type_name() const override { return "DerivedValueY"; }
+ FORY_STRUCT(DerivedValueY, FORY_BASE(BaseValue), priority, tags);
};
-// FORY_STRUCT macro invocations for polymorphic DERIVED types only
-// Must be inside the namespace where the types are defined
-FORY_STRUCT(DerivedKeyA, id, data);
-FORY_STRUCT(DerivedKeyB, id, value);
-FORY_STRUCT(DerivedValueX, name, priority);
-FORY_STRUCT(DerivedValueY, name, priority, tags);
-
} // namespace polymorphic_test
// Type IDs for polymorphic types
diff --git a/cpp/fory/serialization/serialization_test.cc
b/cpp/fory/serialization/serialization_test.cc
index bf584bdbd..9b7ee8e98 100644
--- a/cpp/fory/serialization/serialization_test.cc
+++ b/cpp/fory/serialization/serialization_test.cc
@@ -31,7 +31,7 @@
#include "fory/type/type.h"
// ============================================================================
-// Test Struct Definitions (must be at global scope for FORY_STRUCT)
+// Test Struct Definitions (FORY_STRUCT is declared inside each struct)
// ============================================================================
struct SimpleStruct {
@@ -41,10 +41,9 @@ struct SimpleStruct {
bool operator==(const SimpleStruct &other) const {
return x == other.x && y == other.y;
}
+ FORY_STRUCT(SimpleStruct, x, y);
};
-FORY_STRUCT(SimpleStruct, x, y);
-
struct ComplexStruct {
std::string name;
int32_t age;
@@ -53,10 +52,9 @@ struct ComplexStruct {
bool operator==(const ComplexStruct &other) const {
return name == other.name && age == other.age && hobbies == other.hobbies;
}
+ FORY_STRUCT(ComplexStruct, name, age, hobbies);
};
-FORY_STRUCT(ComplexStruct, name, age, hobbies);
-
struct NestedStruct {
SimpleStruct point;
std::string label;
@@ -64,10 +62,9 @@ struct NestedStruct {
bool operator==(const NestedStruct &other) const {
return point == other.point && label == other.label;
}
+ FORY_STRUCT(NestedStruct, point, label);
};
-FORY_STRUCT(NestedStruct, point, label);
-
enum class Color { RED, GREEN, BLUE };
enum class LegacyStatus : int32_t { NEG = -3, ZERO = 0, LARGE = 42 };
FORY_ENUM(LegacyStatus, NEG, ZERO, LARGE);
diff --git a/cpp/fory/serialization/serializer_traits.h
b/cpp/fory/serialization/serializer_traits.h
index 786777690..cd3ea0480 100644
--- a/cpp/fory/serialization/serializer_traits.h
+++ b/cpp/fory/serialization/serializer_traits.h
@@ -216,9 +216,9 @@ inline const nullable_element_t<T> &deref_nullable(const T
&value) {
// Fory Struct Detection
// ============================================================================
-/// Check if type has FORY_FIELD_INFO defined via ADL
+/// Check if type has FORY_STRUCT defined via member lookup or ADL.
/// This trait only evaluates to true if ForyFieldInfo is available AND doesn't
-/// trigger static_assert
+/// trigger static_assert.
template <typename T, typename = void>
struct has_fory_field_info : std::false_type {};
@@ -230,7 +230,7 @@ struct has_fory_field_info<
template <typename T>
inline constexpr bool has_fory_field_info_v = has_fory_field_info<T>::value;
-/// Check if type is serializable (has both FORY_FIELD_INFO and
+/// Check if type is serializable (has both FORY_STRUCT and
/// SerializationMeta)
template <typename T, typename = void>
struct is_fory_serializable : std::false_type {};
diff --git a/cpp/fory/serialization/smart_ptr_serializer_test.cc
b/cpp/fory/serialization/smart_ptr_serializer_test.cc
index d69eaff97..8c4b03c29 100644
--- a/cpp/fory/serialization/smart_ptr_serializer_test.cc
+++ b/cpp/fory/serialization/smart_ptr_serializer_test.cc
@@ -29,24 +29,24 @@ namespace serialization {
struct OptionalIntHolder {
std::optional<int32_t> value;
+ FORY_STRUCT(OptionalIntHolder, value);
};
-FORY_STRUCT(OptionalIntHolder, value);
struct OptionalSharedHolder {
std::optional<std::shared_ptr<int32_t>> value;
+ FORY_STRUCT(OptionalSharedHolder, value);
};
-FORY_STRUCT(OptionalSharedHolder, value);
struct SharedPair {
std::shared_ptr<int32_t> first;
std::shared_ptr<int32_t> second;
+ FORY_STRUCT(SharedPair, first, second);
};
-FORY_STRUCT(SharedPair, first, second);
struct UniqueHolder {
std::unique_ptr<int32_t> value;
+ FORY_STRUCT(UniqueHolder, value);
};
-FORY_STRUCT(UniqueHolder, value);
namespace {
@@ -188,30 +188,30 @@ struct Base {
virtual ~Base() = default;
virtual std::string get_type() const = 0;
int32_t base_value = 0;
+ FORY_STRUCT(Base, base_value);
};
-FORY_STRUCT(Base, base_value);
struct Derived1 : Base {
std::string get_type() const override { return "Derived1"; }
std::string derived1_data;
+ FORY_STRUCT(Derived1, FORY_BASE(Base), derived1_data);
};
-FORY_STRUCT(Derived1, base_value, derived1_data);
struct Derived2 : Base {
std::string get_type() const override { return "Derived2"; }
int32_t derived2_data = 0;
+ FORY_STRUCT(Derived2, FORY_BASE(Base), derived2_data);
};
-FORY_STRUCT(Derived2, base_value, derived2_data);
struct PolymorphicSharedHolder {
std::shared_ptr<Base> ptr;
+ FORY_STRUCT(PolymorphicSharedHolder, ptr);
};
-FORY_STRUCT(PolymorphicSharedHolder, ptr);
struct PolymorphicUniqueHolder {
std::unique_ptr<Base> ptr;
+ FORY_STRUCT(PolymorphicUniqueHolder, ptr);
};
-FORY_STRUCT(PolymorphicUniqueHolder, ptr);
TEST(SmartPtrSerializerTest, PolymorphicSharedPtrDerived1) {
auto fory = create_serializer(true);
@@ -334,14 +334,14 @@ struct NestedContainer {
virtual ~NestedContainer() = default;
int32_t value = 0;
std::shared_ptr<NestedContainer> nested;
+ FORY_STRUCT(NestedContainer, value, nested);
};
-FORY_STRUCT(NestedContainer, value, nested);
// Holder struct to wrap nested container in a polymorphic shared_ptr
struct NestedContainerHolder {
std::shared_ptr<NestedContainer> ptr;
+ FORY_STRUCT(NestedContainerHolder, ptr);
};
-FORY_STRUCT(NestedContainerHolder, ptr);
TEST(SmartPtrSerializerTest, MaxDynDepthExceeded) {
// Create Fory with max_dyn_depth=2
@@ -443,8 +443,8 @@ struct PolymorphicBaseForMono {
virtual std::string name() const { return "PolymorphicBaseForMono"; }
int32_t value = 0;
std::string data;
+ FORY_STRUCT(PolymorphicBaseForMono, value, data);
};
-FORY_STRUCT(PolymorphicBaseForMono, value, data);
// Holder with non-dynamic field using fory::field<>
struct NonDynamicFieldHolder {
@@ -453,8 +453,8 @@ struct NonDynamicFieldHolder {
fory::field<std::shared_ptr<PolymorphicBaseForMono>, 0, fory::nullable,
fory::dynamic<false>>
ptr;
+ FORY_STRUCT(NonDynamicFieldHolder, ptr);
};
-FORY_STRUCT(NonDynamicFieldHolder, ptr);
TEST(SmartPtrSerializerTest, NonDynamicFieldWithForyField) {
NonDynamicFieldHolder original;
diff --git a/cpp/fory/serialization/struct_compatible_test.cc
b/cpp/fory/serialization/struct_compatible_test.cc
index 0cabf9476..5777447cf 100644
--- a/cpp/fory/serialization/struct_compatible_test.cc
+++ b/cpp/fory/serialization/struct_compatible_test.cc
@@ -49,8 +49,8 @@ struct PersonV1 {
bool operator==(const PersonV1 &other) const {
return name == other.name && age == other.age;
}
+ FORY_STRUCT(PersonV1, name, age);
};
-FORY_STRUCT(PersonV1, name, age);
// V2: Added email field
struct PersonV2 {
@@ -61,8 +61,8 @@ struct PersonV2 {
bool operator==(const PersonV2 &other) const {
return name == other.name && age == other.age && email == other.email;
}
+ FORY_STRUCT(PersonV2, name, age, email);
};
-FORY_STRUCT(PersonV2, name, age, email);
// V3: Added multiple fields
struct PersonV3 {
@@ -76,8 +76,8 @@ struct PersonV3 {
return name == other.name && age == other.age && email == other.email &&
phone == other.phone && address == other.address;
}
+ FORY_STRUCT(PersonV3, name, age, email, phone, address);
};
-FORY_STRUCT(PersonV3, name, age, email, phone, address);
// ============================================================================
// Test Case 2: Removing Fields (Forward Compatibility)
@@ -96,8 +96,8 @@ struct UserFull {
email == other.email && password_hash == other.password_hash &&
login_count == other.login_count;
}
+ FORY_STRUCT(UserFull, id, username, email, password_hash, login_count);
};
-FORY_STRUCT(UserFull, id, username, email, password_hash, login_count);
// Minimal schema (removed 3 fields)
struct UserMinimal {
@@ -107,8 +107,8 @@ struct UserMinimal {
bool operator==(const UserMinimal &other) const {
return id == other.id && username == other.username;
}
+ FORY_STRUCT(UserMinimal, id, username);
};
-FORY_STRUCT(UserMinimal, id, username);
// ============================================================================
// Test Case 3: Field Reordering
@@ -124,8 +124,8 @@ struct ConfigOriginal {
return host == other.host && port == other.port &&
enable_ssl == other.enable_ssl && protocol == other.protocol;
}
+ FORY_STRUCT(ConfigOriginal, host, port, enable_ssl, protocol);
};
-FORY_STRUCT(ConfigOriginal, host, port, enable_ssl, protocol);
// Reordered fields (different order)
struct ConfigReordered {
@@ -138,8 +138,8 @@ struct ConfigReordered {
return host == other.host && port == other.port &&
enable_ssl == other.enable_ssl && protocol == other.protocol;
}
+ FORY_STRUCT(ConfigReordered, enable_ssl, protocol, host, port);
};
-FORY_STRUCT(ConfigReordered, enable_ssl, protocol, host, port);
// ============================================================================
// Test Case 4: Nested Struct Evolution
@@ -152,8 +152,8 @@ struct AddressV1 {
bool operator==(const AddressV1 &other) const {
return street == other.street && city == other.city;
}
+ FORY_STRUCT(AddressV1, street, city);
};
-FORY_STRUCT(AddressV1, street, city);
struct AddressV2 {
std::string street;
@@ -165,8 +165,8 @@ struct AddressV2 {
return street == other.street && city == other.city &&
country == other.country && zipcode == other.zipcode;
}
+ FORY_STRUCT(AddressV2, street, city, country, zipcode);
};
-FORY_STRUCT(AddressV2, street, city, country, zipcode);
struct EmployeeV1 {
std::string name;
@@ -175,8 +175,8 @@ struct EmployeeV1 {
bool operator==(const EmployeeV1 &other) const {
return name == other.name && home_address == other.home_address;
}
+ FORY_STRUCT(EmployeeV1, name, home_address);
};
-FORY_STRUCT(EmployeeV1, name, home_address);
struct EmployeeV2 {
std::string name;
@@ -187,8 +187,8 @@ struct EmployeeV2 {
return name == other.name && home_address == other.home_address &&
employee_id == other.employee_id;
}
+ FORY_STRUCT(EmployeeV2, name, home_address, employee_id);
};
-FORY_STRUCT(EmployeeV2, name, home_address, employee_id);
// ============================================================================
// Test Case 5: Collection Field Evolution
@@ -201,8 +201,8 @@ struct ProductV1 {
bool operator==(const ProductV1 &other) const {
return name == other.name && price == other.price;
}
+ FORY_STRUCT(ProductV1, name, price);
};
-FORY_STRUCT(ProductV1, name, price);
struct ProductV2 {
std::string name;
@@ -214,8 +214,8 @@ struct ProductV2 {
return name == other.name && price == other.price && tags == other.tags &&
attributes == other.attributes;
}
+ FORY_STRUCT(ProductV2, name, price, tags, attributes);
};
-FORY_STRUCT(ProductV2, name, price, tags, attributes);
// ============================================================================
// TESTS
diff --git a/cpp/fory/serialization/struct_serializer.h
b/cpp/fory/serialization/struct_serializer.h
index 11bc7a9e0..efa396e62 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -43,6 +43,8 @@
namespace fory {
namespace serialization {
+using meta::ForyFieldInfo;
+
/// Field type markers for collection fields in compatible/evolution mode.
/// These match Java's FieldResolver.FieldTypes values.
constexpr int8_t FIELD_TYPE_OBJECT = 0;
@@ -54,87 +56,21 @@ constexpr int8_t FIELD_TYPE_MAP_KV_FINAL = 4;
/// Serialization metadata for a type.
///
/// This template is populated automatically when `FORY_STRUCT` is used to
-/// register a type. The registration macro defines an ADL-visible marker
-/// function which this trait detects in order to enable serialization. The
-/// field count is derived from the generated `ForyFieldInfo` metadata.
+/// register a type. The registration macro defines a constexpr metadata
+/// function that is discovered via member lookup or ADL. The field count is
+/// derived from the generated `ForyFieldInfo` metadata.
template <typename T, typename Enable> struct SerializationMeta {
static constexpr bool is_serializable = false;
static constexpr size_t field_count = 0;
};
template <typename T>
-struct SerializationMeta<
- T, std::void_t<decltype(ForyStructMarker(std::declval<const T &>()))>> {
+struct SerializationMeta<T,
+ std::enable_if_t<meta::HasForyStructInfo<T>::value>> {
static constexpr bool is_serializable = true;
static constexpr size_t field_count =
decltype(ForyFieldInfo(std::declval<const T &>()))::Size;
};
-/// Main serialization registration macro.
-///
-/// This macro must be placed in the same namespace as the type for ADL
-/// (Argument-Dependent Lookup).
-///
-/// It builds upon FORY_FIELD_INFO to add serialization-specific metadata:
-/// - Marks the type as serializable
-/// - Provides compile-time metadata access
-///
-/// Example:
-/// ```cpp
-/// namespace myapp {
-/// struct Person {
-/// std::string name;
-/// int32_t age;
-/// };
-/// FORY_STRUCT(Person, name, age);
-/// }
-/// ```
-///
-/// After expansion, the type can be serialized using Fory:
-/// ```cpp
-/// fory::serialization::Fory fory;
-/// myapp::Person person{"Alice", 30};
-/// auto bytes = fory.serialize(person);
-/// ```
-/// Main struct registration macro.
-/// TypeIndex uses the fallback (type_fallback_hash based on PRETTY_FUNCTION)
-/// which provides unique type identification without namespace issues.
-#define FORY_STRUCT_TYPE_ONLY(Type)
\
- static_assert(std::is_class_v<Type>, "it must be a class type");
\
- template <typename> struct ForyFieldInfoImpl;
\
- template <> struct ForyFieldInfoImpl<Type> {
\
- static inline constexpr size_t Size = 0;
\
- static inline constexpr std::string_view Name = #Type;
\
- static inline constexpr std::array<std::string_view, Size> Names = {};
\
- static inline constexpr auto Ptrs = std::tuple{};
\
- };
\
- static_assert(
\
- fory::meta::IsValidFieldInfo<ForyFieldInfoImpl<Type>>(),
\
- "duplicated fields in FORY_FIELD_INFO arguments are detected");
\
- inline constexpr auto ForyFieldInfo(const Type &) noexcept {
\
- return ForyFieldInfoImpl<Type>{};
\
- }
\
- inline constexpr std::true_type ForyStructMarker(const Type &) noexcept {
\
- return {};
\
- }
\
- static_assert(static_cast<std::true_type (*)(const Type &) noexcept>(
\
- &ForyStructMarker) != nullptr,
\
- "ForyStructMarker must be declared");
-
-#define FORY_STRUCT_WITH_FIELDS(Type, ...)
\
- FORY_FIELD_INFO(Type, __VA_ARGS__)
\
- inline constexpr std::true_type ForyStructMarker(const Type &) noexcept {
\
- return {};
\
- }
\
- static_assert(static_cast<std::true_type (*)(const Type &) noexcept>(
\
- &ForyStructMarker) != nullptr,
\
- "ForyStructMarker must be declared");
-
-#define FORY_STRUCT_1(Type, ...) FORY_STRUCT_TYPE_ONLY(Type)
-#define FORY_STRUCT_0(Type, ...) FORY_STRUCT_WITH_FIELDS(Type, __VA_ARGS__)
-
-#define FORY_STRUCT(Type, ...)
\
- FORY_PP_CONCAT(FORY_STRUCT_, FORY_PP_IS_EMPTY(__VA_ARGS__))(Type,
__VA_ARGS__)
-
namespace detail {
/// Helper to check if a TypeId represents a primitive type.
@@ -558,7 +494,7 @@ template <typename T> struct CompileTimeFieldHelpers {
using FieldDescriptor = decltype(ForyFieldInfo(std::declval<const T &>()));
static constexpr size_t FieldCount = FieldDescriptor::Size;
static inline constexpr auto Names = FieldDescriptor::Names;
- static inline constexpr auto Ptrs = FieldDescriptor::Ptrs;
+ static inline constexpr auto Ptrs = FieldDescriptor::Ptrs();
using FieldPtrs = decltype(Ptrs);
template <size_t Index> static constexpr uint32_t field_type_id() {
@@ -1590,7 +1526,8 @@ FORY_ALWAYS_INLINE void write_single_fixed_field(const T
&obj, Buffer &buffer,
constexpr size_t field_offset =
compute_fixed_field_write_offset<T, SortedIdx>();
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptr = std::get<original_index>(decltype(field_info)::Ptrs);
+ const auto field_ptr =
+ std::get<original_index>(decltype(field_info)::PtrsRef());
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
@@ -1631,7 +1568,8 @@ FORY_ALWAYS_INLINE void write_single_varint_field(const T
&obj, Buffer &buffer,
using Helpers = CompileTimeFieldHelpers<T>;
constexpr size_t original_index = Helpers::sorted_indices[SortedPos];
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptr = std::get<original_index>(decltype(field_info)::Ptrs);
+ const auto field_ptr =
+ std::get<original_index>(decltype(field_info)::PtrsRef());
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
@@ -1673,7 +1611,8 @@ write_single_remaining_field(const T &obj, Buffer
&buffer, uint32_t &offset) {
using Helpers = CompileTimeFieldHelpers<T>;
constexpr size_t original_index = Helpers::sorted_indices[SortedPos];
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptr = std::get<original_index>(decltype(field_info)::Ptrs);
+ const auto field_ptr =
+ std::get<original_index>(decltype(field_info)::PtrsRef());
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
@@ -1963,7 +1902,7 @@ void write_field_at_sorted_position(const T &obj,
WriteContext &ctx,
using Helpers = CompileTimeFieldHelpers<T>;
constexpr size_t original_index = Helpers::sorted_indices[SortedPosition];
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptrs = decltype(field_info)::Ptrs;
+ const auto &field_ptrs = decltype(field_info)::PtrsRef();
write_single_field<T, original_index>(obj, ctx, field_ptrs, has_generics);
}
@@ -2154,7 +2093,7 @@ template <size_t Index, typename T>
void read_single_field_by_index(T &obj, ReadContext &ctx) {
using Helpers = CompileTimeFieldHelpers<T>;
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptrs = decltype(field_info)::Ptrs;
+ const auto &field_ptrs = decltype(field_info)::PtrsRef();
const auto field_ptr = std::get<Index>(field_ptrs);
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
@@ -2339,7 +2278,7 @@ void read_single_field_by_index_compatible(T &obj,
ReadContext &ctx,
uint32_t remote_type_id) {
using Helpers = CompileTimeFieldHelpers<T>;
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptrs = decltype(field_info)::Ptrs;
+ const auto &field_ptrs = decltype(field_info)::PtrsRef();
const auto field_ptr = std::get<Index>(field_ptrs);
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
@@ -2598,7 +2537,8 @@ FORY_ALWAYS_INLINE void read_single_fixed_field(T &obj,
Buffer &buffer,
constexpr size_t original_index = Helpers::sorted_indices[SortedIdx];
constexpr size_t field_offset = compute_fixed_field_offset<T, SortedIdx>();
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptr = std::get<original_index>(decltype(field_info)::Ptrs);
+ const auto field_ptr =
+ std::get<original_index>(decltype(field_info)::PtrsRef());
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
@@ -2676,7 +2616,8 @@ FORY_ALWAYS_INLINE void read_single_varint_field(T &obj,
Buffer &buffer,
using Helpers = CompileTimeFieldHelpers<T>;
constexpr size_t original_index = Helpers::sorted_indices[SortedPos];
const auto field_info = ForyFieldInfo(obj);
- const auto field_ptr = std::get<original_index>(decltype(field_info)::Ptrs);
+ const auto field_ptr =
+ std::get<original_index>(decltype(field_info)::PtrsRef());
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
diff --git a/cpp/fory/serialization/struct_test.cc
b/cpp/fory/serialization/struct_test.cc
index 2a5882d26..4d5d55cb5 100644
--- a/cpp/fory/serialization/struct_test.cc
+++ b/cpp/fory/serialization/struct_test.cc
@@ -39,7 +39,8 @@
#include <vector>
// ============================================================================
-// ALL STRUCT DEFINITIONS MUST BE AT GLOBAL SCOPE (for FORY_STRUCT macro)
+// FORY_STRUCT can be declared inside the struct/class definition or at
+// namespace scope.
// ============================================================================
// Edge cases
@@ -48,8 +49,8 @@ struct SingleFieldStruct {
bool operator==(const SingleFieldStruct &other) const {
return value == other.value;
}
+ FORY_STRUCT(SingleFieldStruct, value);
};
-FORY_STRUCT(SingleFieldStruct, value);
struct TwoFieldStruct {
int32_t x;
@@ -57,8 +58,8 @@ struct TwoFieldStruct {
bool operator==(const TwoFieldStruct &other) const {
return x == other.x && y == other.y;
}
+ FORY_STRUCT(TwoFieldStruct, x, y);
};
-FORY_STRUCT(TwoFieldStruct, x, y);
struct ManyFieldsStruct {
bool b1;
@@ -75,8 +76,27 @@ struct ManyFieldsStruct {
i32 == other.i32 && i64 == other.i64 && f32 == other.f32 &&
f64 == other.f64 && str == other.str;
}
+ FORY_STRUCT(ManyFieldsStruct, b1, i8, i16, i32, i64, f32, f64, str);
+};
+
+class PrivateFieldsStruct {
+public:
+ PrivateFieldsStruct() = default;
+ PrivateFieldsStruct(int32_t id, std::string name, std::vector<int32_t>
scores)
+ : id_(id), name_(std::move(name)), scores_(std::move(scores)) {}
+
+ bool operator==(const PrivateFieldsStruct &other) const {
+ return id_ == other.id_ && name_ == other.name_ && scores_ ==
other.scores_;
+ }
+
+private:
+ int32_t id_ = 0;
+ std::string name_;
+ std::vector<int32_t> scores_;
+
+public:
+ FORY_STRUCT(PrivateFieldsStruct, id_, name_, scores_);
};
-FORY_STRUCT(ManyFieldsStruct, b1, i8, i16, i32, i64, f32, f64, str);
// All primitives
struct AllPrimitivesStruct {
@@ -100,10 +120,10 @@ struct AllPrimitivesStruct {
uint64_val == other.uint64_val && float_val == other.float_val &&
double_val == other.double_val;
}
+ FORY_STRUCT(AllPrimitivesStruct, bool_val, int8_val, int16_val, int32_val,
+ int64_val, uint8_val, uint16_val, uint32_val, uint64_val,
+ float_val, double_val);
};
-FORY_STRUCT(AllPrimitivesStruct, bool_val, int8_val, int16_val, int32_val,
- int64_val, uint8_val, uint16_val, uint32_val, uint64_val,
float_val,
- double_val);
// String handling
struct StringTestStruct {
@@ -116,8 +136,8 @@ struct StringTestStruct {
return empty == other.empty && ascii == other.ascii && utf8 == other.utf8
&&
long_text == other.long_text;
}
+ FORY_STRUCT(StringTestStruct, empty, ascii, utf8, long_text);
};
-FORY_STRUCT(StringTestStruct, empty, ascii, utf8, long_text);
// Nested structs
struct Point2D {
@@ -126,8 +146,8 @@ struct Point2D {
bool operator==(const Point2D &other) const {
return x == other.x && y == other.y;
}
+ FORY_STRUCT(Point2D, x, y);
};
-FORY_STRUCT(Point2D, x, y);
struct Point3D {
int32_t x;
@@ -136,8 +156,8 @@ struct Point3D {
bool operator==(const Point3D &other) const {
return x == other.x && y == other.y && z == other.z;
}
+ FORY_STRUCT(Point3D, x, y, z);
};
-FORY_STRUCT(Point3D, x, y, z);
struct Rectangle {
Point2D top_left;
@@ -145,8 +165,8 @@ struct Rectangle {
bool operator==(const Rectangle &other) const {
return top_left == other.top_left && bottom_right == other.bottom_right;
}
+ FORY_STRUCT(Rectangle, top_left, bottom_right);
};
-FORY_STRUCT(Rectangle, top_left, bottom_right);
struct BoundingBox {
Rectangle bounds;
@@ -154,8 +174,8 @@ struct BoundingBox {
bool operator==(const BoundingBox &other) const {
return bounds == other.bounds && label == other.label;
}
+ FORY_STRUCT(BoundingBox, bounds, label);
};
-FORY_STRUCT(BoundingBox, bounds, label);
struct Scene {
Point3D camera;
@@ -165,8 +185,8 @@ struct Scene {
return camera == other.camera && light == other.light &&
viewport == other.viewport;
}
+ FORY_STRUCT(Scene, camera, light, viewport);
};
-FORY_STRUCT(Scene, camera, light, viewport);
// Containers
struct VectorStruct {
@@ -178,8 +198,8 @@ struct VectorStruct {
return numbers == other.numbers && strings == other.strings &&
points == other.points;
}
+ FORY_STRUCT(VectorStruct, numbers, strings, points);
};
-FORY_STRUCT(VectorStruct, numbers, strings, points);
struct MapStruct {
std::map<std::string, int32_t> str_to_int;
@@ -190,8 +210,8 @@ struct MapStruct {
return str_to_int == other.str_to_int && int_to_str == other.int_to_str &&
named_points == other.named_points;
}
+ FORY_STRUCT(MapStruct, str_to_int, int_to_str, named_points);
};
-FORY_STRUCT(MapStruct, str_to_int, int_to_str, named_points);
struct NestedContainerStruct {
std::vector<std::vector<int32_t>> matrix;
@@ -200,8 +220,8 @@ struct NestedContainerStruct {
bool operator==(const NestedContainerStruct &other) const {
return matrix == other.matrix && grouped_numbers == other.grouped_numbers;
}
+ FORY_STRUCT(NestedContainerStruct, matrix, grouped_numbers);
};
-FORY_STRUCT(NestedContainerStruct, matrix, grouped_numbers);
// Optional fields
struct OptionalFieldsStruct {
@@ -214,8 +234,8 @@ struct OptionalFieldsStruct {
return name == other.name && age == other.age && email == other.email &&
location == other.location;
}
+ FORY_STRUCT(OptionalFieldsStruct, name, age, email, location);
};
-FORY_STRUCT(OptionalFieldsStruct, name, age, email, location);
// Enums
enum class Color { RED = 0, GREEN = 1, BLUE = 2 };
@@ -227,8 +247,8 @@ struct EnumStruct {
bool operator==(const EnumStruct &other) const {
return color == other.color && status == other.status;
}
+ FORY_STRUCT(EnumStruct, color, status);
};
-FORY_STRUCT(EnumStruct, color, status);
// Real-world scenarios
struct UserProfile {
@@ -248,9 +268,9 @@ struct UserProfile {
follower_count == other.follower_count &&
is_verified == other.is_verified;
}
+ FORY_STRUCT(UserProfile, user_id, username, email, bio, interests, metadata,
+ follower_count, is_verified);
};
-FORY_STRUCT(UserProfile, user_id, username, email, bio, interests, metadata,
- follower_count, is_verified);
struct Product {
int64_t product_id;
@@ -267,9 +287,9 @@ struct Product {
stock == other.stock && tags == other.tags &&
attributes == other.attributes;
}
+ FORY_STRUCT(Product, product_id, name, description, price, stock, tags,
+ attributes);
};
-FORY_STRUCT(Product, product_id, name, description, price, stock, tags,
- attributes);
struct OrderItem {
int64_t product_id;
@@ -280,8 +300,8 @@ struct OrderItem {
return product_id == other.product_id && quantity == other.quantity &&
unit_price == other.unit_price;
}
+ FORY_STRUCT(OrderItem, product_id, quantity, unit_price);
};
-FORY_STRUCT(OrderItem, product_id, quantity, unit_price);
struct Order {
int64_t order_id;
@@ -295,8 +315,53 @@ struct Order {
items == other.items && total_amount == other.total_amount &&
order_status == other.order_status;
}
+ FORY_STRUCT(Order, order_id, customer_id, items, total_amount, order_status);
};
-FORY_STRUCT(Order, order_id, customer_id, items, total_amount, order_status);
+
+namespace nested_test {
+namespace inner {
+
+struct InClassStruct {
+ int32_t id;
+ std::string name;
+ bool operator==(const InClassStruct &other) const {
+ return id == other.id && name == other.name;
+ }
+ FORY_STRUCT(InClassStruct, id, name);
+};
+
+struct OutClassStruct {
+ int32_t id;
+ std::string name;
+ bool operator==(const OutClassStruct &other) const {
+ return id == other.id && name == other.name;
+ }
+};
+
+FORY_STRUCT(OutClassStruct, id, name);
+
+} // namespace inner
+} // namespace nested_test
+
+namespace external_test {
+
+struct ExternalStruct {
+ int32_t id;
+ std::string name;
+ bool operator==(const ExternalStruct &other) const {
+ return id == other.id && name == other.name;
+ }
+};
+
+FORY_STRUCT(ExternalStruct, id, name);
+
+struct ExternalEmpty {
+ bool operator==(const ExternalEmpty & /*other*/) const { return true; }
+};
+
+FORY_STRUCT(ExternalEmpty);
+
+} // namespace external_test
// ============================================================================
// TEST IMPLEMENTATION (Inside namespace)
@@ -314,6 +379,7 @@ inline void register_all_test_types(Fory &fory) {
fory.register_struct<SingleFieldStruct>(type_id++);
fory.register_struct<TwoFieldStruct>(type_id++);
fory.register_struct<ManyFieldsStruct>(type_id++);
+ fory.register_struct<PrivateFieldsStruct>(type_id++);
fory.register_struct<AllPrimitivesStruct>(type_id++);
fory.register_struct<StringTestStruct>(type_id++);
fory.register_struct<Point2D>(type_id++);
@@ -330,6 +396,10 @@ inline void register_all_test_types(Fory &fory) {
fory.register_struct<Product>(type_id++);
fory.register_struct<OrderItem>(type_id++);
fory.register_struct<Order>(type_id++);
+ fory.register_struct<nested_test::inner::InClassStruct>(type_id++);
+ fory.register_struct<nested_test::inner::OutClassStruct>(type_id++);
+ fory.register_struct<external_test::ExternalStruct>(type_id++);
+ fory.register_struct<external_test::ExternalEmpty>(type_id++);
}
template <typename T> void test_roundtrip(const T &original) {
@@ -377,6 +447,10 @@ TEST(StructComprehensiveTest, ManyFieldsStruct) {
-9223372036854775807LL - 1, -1.0f, -1.0,
""});
}
+TEST(StructComprehensiveTest, PrivateFieldsStruct) {
+ test_roundtrip(PrivateFieldsStruct{42, "secret", {1, 2, 3}});
+}
+
TEST(StructComprehensiveTest, AllPrimitivesZero) {
test_roundtrip(AllPrimitivesStruct{false, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f,
0.0});
}
@@ -525,6 +599,23 @@ TEST(StructComprehensiveTest, LargeVectorOfStructs) {
EXPECT_EQ(points, deser_result.value());
}
+TEST(StructComprehensiveTest, NestedNamespaceInClassStruct) {
+ test_roundtrip(nested_test::inner::InClassStruct{7, "in"});
+}
+
+TEST(StructComprehensiveTest, NestedNamespaceOutClassStruct) {
+ test_roundtrip(nested_test::inner::OutClassStruct{8, "out"});
+}
+
+TEST(StructComprehensiveTest, ExternalStruct) {
+ test_roundtrip(external_test::ExternalStruct{1, "external"});
+ test_roundtrip(external_test::ExternalStruct{42, ""});
+}
+
+TEST(StructComprehensiveTest, ExternalEmptyStruct) {
+ test_roundtrip(external_test::ExternalEmpty{});
+}
+
} // namespace test
} // namespace serialization
} // namespace fory
diff --git a/cpp/fory/serialization/tuple_serializer_test.cc
b/cpp/fory/serialization/tuple_serializer_test.cc
index 64c3b19b0..8eed7d7a4 100644
--- a/cpp/fory/serialization/tuple_serializer_test.cc
+++ b/cpp/fory/serialization/tuple_serializer_test.cc
@@ -64,33 +64,33 @@ TEST(TupleSerializerTest, HomogeneityDetection) {
// First test with vector to verify test setup
struct VectorHolder {
std::vector<int32_t> values;
+ FORY_STRUCT(VectorHolder, values);
};
-FORY_STRUCT(VectorHolder, values);
struct TupleHomogeneousHolder {
std::tuple<int32_t, int32_t, int32_t> values;
+ FORY_STRUCT(TupleHomogeneousHolder, values);
};
-FORY_STRUCT(TupleHomogeneousHolder, values);
struct TupleHeterogeneousHolder {
std::tuple<int32_t, std::string, double> values;
+ FORY_STRUCT(TupleHeterogeneousHolder, values);
};
-FORY_STRUCT(TupleHeterogeneousHolder, values);
struct TupleSingleHolder {
std::tuple<std::string> value;
+ FORY_STRUCT(TupleSingleHolder, value);
};
-FORY_STRUCT(TupleSingleHolder, value);
struct TupleEmptyHolder {
std::tuple<> value;
+ FORY_STRUCT(TupleEmptyHolder, value);
};
-FORY_STRUCT(TupleEmptyHolder, value);
struct TupleNestedHolder {
std::tuple<std::tuple<int32_t, int32_t>, std::string> values;
+ FORY_STRUCT(TupleNestedHolder, values);
};
-FORY_STRUCT(TupleNestedHolder, values);
Fory create_fory() {
return Fory::builder().xlang(true).track_ref(true).build();
@@ -232,8 +232,8 @@ struct TupleLargeHolder {
std::tuple<int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t,
int32_t, int32_t, int32_t>
values;
+ FORY_STRUCT(TupleLargeHolder, values);
};
-FORY_STRUCT(TupleLargeHolder, values);
TEST(TupleSerializerTest, LargeTupleRoundTrip) {
auto fory = create_fory();
diff --git a/cpp/fory/serialization/type_resolver.h
b/cpp/fory/serialization/type_resolver.h
index efdd891f4..827243d1f 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -62,6 +62,8 @@
namespace fory {
namespace serialization {
+using meta::ForyFieldInfo;
+
// Forward declarations
class Fory;
class TypeResolver;
@@ -608,7 +610,7 @@ template <typename T, size_t Index> struct FieldInfoBuilder
{
static FieldInfo build() {
const auto meta = ForyFieldInfo(T{});
const auto field_names = decltype(meta)::Names;
- const auto field_ptrs = decltype(meta)::Ptrs;
+ const auto &field_ptrs = decltype(meta)::PtrsRef();
// Convert camelCase field name to snake_case for cross-language
// compatibility
diff --git a/cpp/fory/serialization/unsigned_serializer_test.cc
b/cpp/fory/serialization/unsigned_serializer_test.cc
index 0bf7fe8c5..30196c1de 100644
--- a/cpp/fory/serialization/unsigned_serializer_test.cc
+++ b/cpp/fory/serialization/unsigned_serializer_test.cc
@@ -42,10 +42,9 @@ struct UnsignedStruct {
return u8_val == other.u8_val && u16_val == other.u16_val &&
u32_val == other.u32_val && u64_val == other.u64_val;
}
+ FORY_STRUCT(UnsignedStruct, u8_val, u16_val, u32_val, u64_val);
};
-FORY_STRUCT(UnsignedStruct, u8_val, u16_val, u32_val, u64_val);
-
struct UnsignedArrayStruct {
std::vector<uint8_t> u8_vec;
std::vector<uint16_t> u16_vec;
@@ -56,10 +55,9 @@ struct UnsignedArrayStruct {
return u8_vec == other.u8_vec && u16_vec == other.u16_vec &&
u32_vec == other.u32_vec && u64_vec == other.u64_vec;
}
+ FORY_STRUCT(UnsignedArrayStruct, u8_vec, u16_vec, u32_vec, u64_vec);
};
-FORY_STRUCT(UnsignedArrayStruct, u8_vec, u16_vec, u32_vec, u64_vec);
-
// ============================================================================
// Test Helper for Native Mode (xlang=false)
// Unsigned types are only supported in native mode (xlang=false)
diff --git a/cpp/fory/serialization/variant_serializer_test.cc
b/cpp/fory/serialization/variant_serializer_test.cc
index 1d0a408a6..89688129f 100644
--- a/cpp/fory/serialization/variant_serializer_test.cc
+++ b/cpp/fory/serialization/variant_serializer_test.cc
@@ -34,15 +34,15 @@ struct OldStruct {
int id;
VariantType field1;
VariantType field2;
+ FORY_STRUCT(OldStruct, id, field1, field2);
};
-FORY_STRUCT(OldStruct, id, field1, field2);
// New schema: struct with only one variant field
struct NewStruct {
int id;
VariantType field1;
+ FORY_STRUCT(NewStruct, id, field1);
};
-FORY_STRUCT(NewStruct, id, field1);
} // namespace
// Helper to create a Fory instance
diff --git a/cpp/fory/serialization/weak_ptr_serializer.h
b/cpp/fory/serialization/weak_ptr_serializer.h
index 95eaa801e..f762d6ea8 100644
--- a/cpp/fory/serialization/weak_ptr_serializer.h
+++ b/cpp/fory/serialization/weak_ptr_serializer.h
@@ -56,8 +56,8 @@ namespace serialization {
/// int32_t value;
/// SharedWeak<Node> parent; // Non-owning back-reference
/// std::vector<std::shared_ptr<Node>> children; // Owning references
+/// FORY_STRUCT(Node, value, parent, children);
/// };
-/// FORY_STRUCT(Node, value, parent, children);
///
/// auto parent = std::make_shared<Node>();
/// parent->value = 1;
diff --git a/cpp/fory/serialization/weak_ptr_serializer_test.cc
b/cpp/fory/serialization/weak_ptr_serializer_test.cc
index 4da9d5300..bd34c6dd1 100644
--- a/cpp/fory/serialization/weak_ptr_serializer_test.cc
+++ b/cpp/fory/serialization/weak_ptr_serializer_test.cc
@@ -136,36 +136,36 @@ TEST(SharedWeakTest, OwnerEquals) {
struct SimpleStruct {
int32_t value;
+ FORY_STRUCT(SimpleStruct, value);
};
-FORY_STRUCT(SimpleStruct, value);
struct StructWithWeak {
int32_t id;
SharedWeak<SimpleStruct> weak_ref;
+ FORY_STRUCT(StructWithWeak, id, weak_ref);
};
-FORY_STRUCT(StructWithWeak, id, weak_ref);
struct StructWithBothRefs {
int32_t id;
std::shared_ptr<SimpleStruct> strong_ref;
SharedWeak<SimpleStruct> weak_ref;
+ FORY_STRUCT(StructWithBothRefs, id, strong_ref, weak_ref);
};
-FORY_STRUCT(StructWithBothRefs, id, strong_ref, weak_ref);
struct MultipleWeakRefsWithOwner {
std::shared_ptr<SimpleStruct> owner;
SharedWeak<SimpleStruct> weak1;
SharedWeak<SimpleStruct> weak2;
SharedWeak<SimpleStruct> weak3;
+ FORY_STRUCT(MultipleWeakRefsWithOwner, owner, weak1, weak2, weak3);
};
-FORY_STRUCT(MultipleWeakRefsWithOwner, owner, weak1, weak2, weak3);
struct NodeWithParent {
int32_t value;
SharedWeak<NodeWithParent> parent;
std::vector<std::shared_ptr<NodeWithParent>> children;
+ FORY_STRUCT(NodeWithParent, value, parent, children);
};
-FORY_STRUCT(NodeWithParent, value, parent, children);
// ============================================================================
// Serialization Tests
diff --git a/cpp/fory/serialization/xlang_test_main.cc
b/cpp/fory/serialization/xlang_test_main.cc
index 299ae8e70..f13823268 100644
--- a/cpp/fory/serialization/xlang_test_main.cc
+++ b/cpp/fory/serialization/xlang_test_main.cc
@@ -114,8 +114,8 @@ FORY_ENUM(Color, Green, Red, Blue, White);
struct Item {
std::string name;
bool operator==(const Item &other) const { return name == other.name; }
+ FORY_STRUCT(Item, name);
};
-FORY_STRUCT(Item, name);
struct SimpleStruct {
std::map<int32_t, double> f1;
@@ -132,8 +132,8 @@ struct SimpleStruct {
f4 == other.f4 && f5 == other.f5 && f6 == other.f6 &&
f7 == other.f7 && f8 == other.f8 && last == other.last;
}
+ FORY_STRUCT(SimpleStruct, f1, f2, f3, f4, f5, f6, f7, f8, last);
};
-FORY_STRUCT(SimpleStruct, f1, f2, f3, f4, f5, f6, f7, f8, last);
// Integer struct used for cross-language boxed integer tests.
// Java xlang mode: all fields are non-nullable by default.
@@ -148,16 +148,16 @@ struct Item1 {
return f1 == other.f1 && f2 == other.f2 && f3 == other.f3 &&
f4 == other.f4 && f5 == other.f5 && f6 == other.f6;
}
+ FORY_STRUCT(Item1, f1, f2, f3, f4, f5, f6);
};
-FORY_STRUCT(Item1, f1, f2, f3, f4, f5, f6);
struct MyStruct {
int32_t id;
MyStruct() = default;
explicit MyStruct(int32_t v) : id(v) {}
bool operator==(const MyStruct &other) const { return id == other.id; }
+ FORY_STRUCT(MyStruct, id);
};
-FORY_STRUCT(MyStruct, id);
struct MyExt {
int32_t id;
@@ -178,16 +178,16 @@ struct MyWrapper {
return color == other.color && my_struct == other.my_struct &&
my_ext == other.my_ext;
}
+ FORY_STRUCT(MyWrapper, color, my_struct, my_ext);
};
-FORY_STRUCT(MyWrapper, color, my_struct, my_ext);
struct EmptyWrapper {
bool operator==(const EmptyWrapper &other) const {
(void)other;
return true;
}
+ FORY_STRUCT(EmptyWrapper);
};
-FORY_STRUCT(EmptyWrapper);
struct VersionCheckStruct {
int32_t f1;
@@ -196,24 +196,24 @@ struct VersionCheckStruct {
bool operator==(const VersionCheckStruct &other) const {
return f1 == other.f1 && f2 == other.f2 && f3 == other.f3;
}
+ FORY_STRUCT(VersionCheckStruct, f1, f2, f3);
};
-FORY_STRUCT(VersionCheckStruct, f1, f2, f3);
struct StructWithList {
std::vector<std::string> items;
bool operator==(const StructWithList &other) const {
return items == other.items;
}
+ FORY_STRUCT(StructWithList, items);
};
-FORY_STRUCT(StructWithList, items);
struct StructWithMap {
std::map<std::string, std::string> data;
bool operator==(const StructWithMap &other) const {
return data == other.data;
}
+ FORY_STRUCT(StructWithMap, data);
};
-FORY_STRUCT(StructWithMap, data);
// ============================================================================
// Polymorphic Container Test Types - Using virtual base class
@@ -223,30 +223,30 @@ struct Animal {
virtual ~Animal() = default;
virtual std::string speak() const = 0;
int32_t age = 0;
+ FORY_STRUCT(Animal, age);
};
-FORY_STRUCT(Animal, age);
struct Dog : Animal {
std::string speak() const override { return "Woof"; }
std::optional<std::string> name;
+ FORY_STRUCT(Dog, FORY_BASE(Animal), name);
};
-FORY_STRUCT(Dog, age, name);
struct Cat : Animal {
std::string speak() const override { return "Meow"; }
int32_t lives = 9;
+ FORY_STRUCT(Cat, FORY_BASE(Animal), lives);
};
-FORY_STRUCT(Cat, age, lives);
struct AnimalListHolder {
std::vector<std::shared_ptr<Animal>> animals;
+ FORY_STRUCT(AnimalListHolder, animals);
};
-FORY_STRUCT(AnimalListHolder, animals);
struct AnimalMapHolder {
std::map<std::string, std::shared_ptr<Animal>> animal_map;
+ FORY_STRUCT(AnimalMapHolder, animal_map);
};
-FORY_STRUCT(AnimalMapHolder, animal_map);
// ============================================================================
// Schema Evolution Test Types
@@ -257,16 +257,16 @@ struct EmptyStructEvolution {
bool operator==(const EmptyStructEvolution &other) const {
return placeholder == other.placeholder;
}
+ FORY_STRUCT(EmptyStructEvolution, placeholder);
};
-FORY_STRUCT(EmptyStructEvolution, placeholder);
struct OneStringFieldStruct {
std::optional<std::string> f1;
bool operator==(const OneStringFieldStruct &other) const {
return f1 == other.f1;
}
+ FORY_STRUCT(OneStringFieldStruct, f1);
};
-FORY_STRUCT(OneStringFieldStruct, f1);
struct TwoStringFieldStruct {
std::string f1;
@@ -274,8 +274,8 @@ struct TwoStringFieldStruct {
bool operator==(const TwoStringFieldStruct &other) const {
return f1 == other.f1 && f2 == other.f2;
}
+ FORY_STRUCT(TwoStringFieldStruct, f1, f2);
};
-FORY_STRUCT(TwoStringFieldStruct, f1, f2);
enum class TestEnum : int32_t { VALUE_A = 0, VALUE_B = 1, VALUE_C = 2 };
FORY_ENUM(TestEnum, VALUE_A, VALUE_B, VALUE_C);
@@ -285,8 +285,8 @@ struct OneEnumFieldStruct {
bool operator==(const OneEnumFieldStruct &other) const {
return f1 == other.f1;
}
+ FORY_STRUCT(OneEnumFieldStruct, f1);
};
-FORY_STRUCT(OneEnumFieldStruct, f1);
struct TwoEnumFieldStruct {
TestEnum f1;
@@ -294,8 +294,8 @@ struct TwoEnumFieldStruct {
bool operator==(const TwoEnumFieldStruct &other) const {
return f1 == other.f1 && f2 == other.f2;
}
+ FORY_STRUCT(TwoEnumFieldStruct, f1, f2);
};
-FORY_STRUCT(TwoEnumFieldStruct, f1, f2);
// ============================================================================
// Nullable Field Test Types - Comprehensive versions matching Java structs
@@ -352,6 +352,12 @@ struct NullableComprehensiveSchemaConsistent {
nullable_map == other.nullable_map;
}
+ FORY_STRUCT(NullableComprehensiveSchemaConsistent, byte_field, short_field,
+ int_field, long_field, float_field, double_field, bool_field,
+ string_field, list_field, set_field, map_field, nullable_int,
+ nullable_long, nullable_float, nullable_double, nullable_bool,
+ nullable_string, nullable_list, nullable_set, nullable_map);
+
private:
static bool compare_optional_float(const std::optional<float> &a,
const std::optional<float> &b) {
@@ -371,11 +377,6 @@ private:
return std::abs(*a - *b) < 1e-9;
}
};
-FORY_STRUCT(NullableComprehensiveSchemaConsistent, byte_field, short_field,
- int_field, long_field, float_field, double_field, bool_field,
- string_field, list_field, set_field, map_field, nullable_int,
- nullable_long, nullable_float, nullable_double, nullable_bool,
- nullable_string, nullable_list, nullable_set, nullable_map);
// NullableComprehensiveCompatible (type id 402)
// Matches Java's NullableComprehensiveCompatible for COMPATIBLE mode
@@ -439,6 +440,13 @@ struct NullableComprehensiveCompatible {
nullable_map2 == other.nullable_map2;
}
+ FORY_STRUCT(NullableComprehensiveCompatible, byte_field, short_field,
+ int_field, long_field, float_field, double_field, bool_field,
+ boxed_int, boxed_long, boxed_float, boxed_double, boxed_bool,
+ string_field, list_field, set_field, map_field, nullable_int1,
+ nullable_long1, nullable_float1, nullable_double1,
nullable_bool1,
+ nullable_string2, nullable_list2, nullable_set2, nullable_map2);
+
private:
static bool compare_optional_float(const std::optional<float> &a,
const std::optional<float> &b) {
@@ -458,12 +466,6 @@ private:
return std::abs(*a - *b) < 1e-9;
}
};
-FORY_STRUCT(NullableComprehensiveCompatible, byte_field, short_field,
int_field,
- long_field, float_field, double_field, bool_field, boxed_int,
- boxed_long, boxed_float, boxed_double, boxed_bool, string_field,
- list_field, set_field, map_field, nullable_int1, nullable_long1,
- nullable_float1, nullable_double1, nullable_bool1,
nullable_string2,
- nullable_list2, nullable_set2, nullable_map2);
// ============================================================================
// Reference Tracking Test Types - Cross-language shared reference tests
@@ -480,8 +482,8 @@ struct RefInnerSchemaConsistent {
bool operator!=(const RefInnerSchemaConsistent &other) const {
return !(*this == other);
}
+ FORY_STRUCT(RefInnerSchemaConsistent, id, name);
};
-FORY_STRUCT(RefInnerSchemaConsistent, id, name);
// Outer struct for reference tracking test (SCHEMA_CONSISTENT mode)
// Contains two fields that both point to the same inner object.
@@ -499,8 +501,8 @@ struct RefOuterSchemaConsistent {
*inner2 == *other.inner2);
return inner1_eq && inner2_eq;
}
+ FORY_STRUCT(RefOuterSchemaConsistent, inner1, inner2);
};
-FORY_STRUCT(RefOuterSchemaConsistent, inner1, inner2);
FORY_FIELD_TAGS(RefOuterSchemaConsistent, (inner1, 0, nullable, ref),
(inner2, 1, nullable, ref));
// Verify field tags are correctly parsed
@@ -529,8 +531,8 @@ struct RefInnerCompatible {
bool operator!=(const RefInnerCompatible &other) const {
return !(*this == other);
}
+ FORY_STRUCT(RefInnerCompatible, id, name);
};
-FORY_STRUCT(RefInnerCompatible, id, name);
// Outer struct for reference tracking test (COMPATIBLE mode)
// Contains two fields that both point to the same inner object.
@@ -548,8 +550,8 @@ struct RefOuterCompatible {
*inner2 == *other.inner2);
return inner1_eq && inner2_eq;
}
+ FORY_STRUCT(RefOuterCompatible, inner1, inner2);
};
-FORY_STRUCT(RefOuterCompatible, inner1, inner2);
FORY_FIELD_TAGS(RefOuterCompatible, (inner1, 0, nullable, ref),
(inner2, 1, nullable, ref));
@@ -577,8 +579,8 @@ struct CircularRefStruct {
(selfRef != nullptr && other.selfRef != nullptr);
return self_eq;
}
+ FORY_STRUCT(CircularRefStruct, name, selfRef);
};
-FORY_STRUCT(CircularRefStruct, name, selfRef);
FORY_FIELD_TAGS(CircularRefStruct, (name, 0), (selfRef, 1, nullable, ref));
// ============================================================================
@@ -595,8 +597,8 @@ struct UnsignedSchemaConsistentSimple {
return u64Tagged == other.u64Tagged &&
u64TaggedNullable == other.u64TaggedNullable;
}
+ FORY_STRUCT(UnsignedSchemaConsistentSimple, u64Tagged, u64TaggedNullable);
};
-FORY_STRUCT(UnsignedSchemaConsistentSimple, u64Tagged, u64TaggedNullable);
FORY_FIELD_CONFIG(UnsignedSchemaConsistentSimple,
UnsignedSchemaConsistentSimple,
(u64Tagged, fory::F().tagged()),
@@ -640,12 +642,12 @@ struct UnsignedSchemaConsistent {
u64FixedNullableField == other.u64FixedNullableField &&
u64TaggedNullableField == other.u64TaggedNullableField;
}
+ FORY_STRUCT(UnsignedSchemaConsistent, u8Field, u16Field, u32VarField,
+ u32FixedField, u64VarField, u64FixedField, u64TaggedField,
+ u8NullableField, u16NullableField, u32VarNullableField,
+ u32FixedNullableField, u64VarNullableField,
u64FixedNullableField,
+ u64TaggedNullableField);
};
-FORY_STRUCT(UnsignedSchemaConsistent, u8Field, u16Field, u32VarField,
- u32FixedField, u64VarField, u64FixedField, u64TaggedField,
- u8NullableField, u16NullableField, u32VarNullableField,
- u32FixedNullableField, u64VarNullableField, u64FixedNullableField,
- u64TaggedNullableField);
// Use new FORY_FIELD_CONFIG with builder pattern for encoding specification
FORY_FIELD_CONFIG(UnsignedSchemaConsistent, UnsignedSchemaConsistent,
(u8Field, fory::F()), (u16Field, fory::F()),
@@ -700,11 +702,11 @@ struct UnsignedSchemaCompatible {
u64FixedField2 == other.u64FixedField2 &&
u64TaggedField2 == other.u64TaggedField2;
}
+ FORY_STRUCT(UnsignedSchemaCompatible, u8Field1, u16Field1, u32VarField1,
+ u32FixedField1, u64VarField1, u64FixedField1, u64TaggedField1,
+ u8Field2, u16Field2, u32VarField2, u32FixedField2, u64VarField2,
+ u64FixedField2, u64TaggedField2);
};
-FORY_STRUCT(UnsignedSchemaCompatible, u8Field1, u16Field1, u32VarField1,
- u32FixedField1, u64VarField1, u64FixedField1, u64TaggedField1,
- u8Field2, u16Field2, u32VarField2, u32FixedField2, u64VarField2,
- u64FixedField2, u64TaggedField2);
// Use new FORY_FIELD_CONFIG with builder pattern for encoding specification
// Group 1: nullable in C++ (std::optional), non-nullable in Java
// Group 2: non-nullable in C++, nullable in Java
diff --git a/cpp/fory/util/pool_test.cc b/cpp/fory/util/pool_test.cc
index 894dc2701..58c4649c5 100644
--- a/cpp/fory/util/pool_test.cc
+++ b/cpp/fory/util/pool_test.cc
@@ -106,8 +106,8 @@ TEST(PoolTest, ConcurrentBorrow) {
threads.reserve(kThreads);
for (size_t t = 0; t < kThreads; ++t) {
- threads.emplace_back([&pool, kIterations]() {
- for (size_t i = 0; i < kIterations; ++i) {
+ threads.emplace_back([&pool, iterations = kIterations]() {
+ for (size_t i = 0; i < iterations; ++i) {
auto value = pool.acquire();
(*value)++;
}
diff --git a/docs/benchmarks/cpp/README.md b/docs/benchmarks/cpp/README.md
index 00b11eed6..a3684c5df 100644
--- a/docs/benchmarks/cpp/README.md
+++ b/docs/benchmarks/cpp/README.md
@@ -36,27 +36,27 @@ python benchmark_report.py --json-file
build/benchmark_results.json --output-dir
| Datatype | Operation | Fory (ns) | Protobuf (ns) | Faster |
| ------------ | ----------- | --------- | ------------- | ----------- |
-| Mediacontent | Serialize | 89.7 | 870.1 | Fory (9.7x) |
-| Mediacontent | Deserialize | 368.4 | 1276.7 | Fory (3.5x) |
-| Sample | Serialize | 59.2 | 95.5 | Fory (1.6x) |
-| Sample | Deserialize | 355.6 | 655.2 | Fory (1.8x) |
-| Struct | Serialize | 23.5 | 40.1 | Fory (1.7x) |
-| Struct | Deserialize | 18.5 | 25.5 | Fory (1.4x) |
+| Mediacontent | Serialize | 443.5 | 1982.5 | Fory (4.5x) |
+| Mediacontent | Deserialize | 1349.0 | 2525.2 | Fory (1.9x) |
+| Sample | Serialize | 235.4 | 309.7 | Fory (1.3x) |
+| Sample | Deserialize | 1068.7 | 1397.0 | Fory (1.3x) |
+| Struct | Serialize | 109.4 | 170.0 | Fory (1.6x) |
+| Struct | Deserialize | 129.1 | 161.2 | Fory (1.2x) |
### Throughput Results (ops/sec)
-| Datatype | Operation | Fory TPS | Protobuf TPS | Faster |
-| ------------ | ----------- | ---------- | ------------ | ----------- |
-| Mediacontent | Serialize | 11,151,839 | 1,149,335 | Fory (9.7x) |
-| Mediacontent | Deserialize | 2,714,139 | 783,243 | Fory (3.5x) |
-| Sample | Serialize | 16,889,474 | 10,474,347 | Fory (1.6x) |
-| Sample | Deserialize | 2,812,439 | 1,526,317 | Fory (1.8x) |
-| Struct | Serialize | 42,570,153 | 24,942,338 | Fory (1.7x) |
-| Struct | Deserialize | 54,146,253 | 39,213,086 | Fory (1.4x) |
+| Datatype | Operation | Fory TPS | Protobuf TPS | Faster |
+| ------------ | ----------- | --------- | ------------ | ----------- |
+| Mediacontent | Serialize | 2,254,915 | 504,410 | Fory (4.5x) |
+| Mediacontent | Deserialize | 741,303 | 396,013 | Fory (1.9x) |
+| Sample | Serialize | 4,248,973 | 3,229,102 | Fory (1.3x) |
+| Sample | Deserialize | 935,709 | 715,837 | Fory (1.3x) |
+| Struct | Serialize | 9,143,618 | 5,881,005 | Fory (1.6x) |
+| Struct | Deserialize | 7,746,787 | 6,202,164 | Fory (1.2x) |
### Serialized Data Sizes (bytes)
| Datatype | Fory | Protobuf |
| -------- | ---- | -------- |
-| Struct | 34 | 61 |
-| Sample | 394 | 375 |
+| Struct | 32 | 61 |
+| Sample | 384 | 375 |
diff --git a/docs/benchmarks/cpp/throughput.png
b/docs/benchmarks/cpp/throughput.png
index ad7696610..e726ebbc1 100644
Binary files a/docs/benchmarks/cpp/throughput.png and
b/docs/benchmarks/cpp/throughput.png differ
diff --git a/docs/guide/cpp/basic-serialization.md
b/docs/guide/cpp/basic-serialization.md
index e45df7e38..3d331450b 100644
--- a/docs/guide/cpp/basic-serialization.md
+++ b/docs/guide/cpp/basic-serialization.md
@@ -177,30 +177,97 @@ Common error types:
## The FORY_STRUCT Macro
-The `FORY_STRUCT` macro registers a struct for serialization:
+The `FORY_STRUCT` macro registers a class for serialization (struct works the
+same way):
```cpp
-struct MyStruct {
+class MyStruct {
+public:
int32_t x;
std::string y;
std::vector<int32_t> z;
+ FORY_STRUCT(MyStruct, x, y, z);
};
+```
+
+Private fields are supported when the macro is placed in a `public:` section:
+
+```cpp
+class PrivateUser {
+public:
+ PrivateUser(int32_t id, std::string name) : id_(id), name_(std::move(name))
{}
+
+ bool operator==(const PrivateUser &other) const {
+ return id_ == other.id_ && name_ == other.name_;
+ }
+
+private:
+ int32_t id_ = 0;
+ std::string name_;
-// Must be in the same namespace as the struct
-FORY_STRUCT(MyStruct, x, y, z);
+public:
+ FORY_STRUCT(PrivateUser, id_, name_);
+};
```
The macro:
1. Generates compile-time field metadata
-2. Enables ADL (Argument-Dependent Lookup) for serialization
+2. Enables member or ADL (Argument-Dependent Lookup) discovery for
serialization
3. Creates efficient serialization code via template specialization
**Requirements:**
-- Must be placed in the same namespace as the struct (for ADL)
+- Must be declared inside the class definition (struct works the same way) or
+ at namespace scope
+- Must be placed after all field declarations (when used inside the class)
+- When used inside a class, the macro must be placed in a `public:` section
- All listed fields must be serializable types
-- Field order in the macro determines serialization order
+- Field order in the macro is not important
+
+## External / Third-Party Types
+
+When you cannot modify a third-party type, use `FORY_STRUCT` at namespace
+scope. This only works with **public** fields.
+
+```cpp
+namespace thirdparty {
+struct Foo {
+ int32_t id;
+ std::string name;
+};
+
+FORY_STRUCT(Foo, id, name);
+} // namespace thirdparty
+```
+
+**Limitations:**
+
+- Must be declared at namespace scope in the same namespace as the type
+- Only public fields are supported
+
+## Inherited Fields
+
+To include base-class fields in a derived type, use `FORY_BASE(Base)` inside
+`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields can be
+referenced.
+
+```cpp
+struct Base {
+ int32_t a;
+ FORY_STRUCT(Base, a);
+};
+
+struct Derived : Base {
+ int32_t b;
+ FORY_STRUCT(Derived, FORY_BASE(Base), b);
+};
+```
+
+**Notes:**
+
+- Base fields are serialized before derived fields.
+- Only fields visible from the derived type are supported.
## Nested Structs
@@ -209,14 +276,14 @@ Nested structs are fully supported:
```cpp
struct Inner {
int32_t value;
+ FORY_STRUCT(Inner, value);
};
-FORY_STRUCT(Inner, value);
struct Outer {
Inner inner;
std::string label;
+ FORY_STRUCT(Outer, inner, label);
};
-FORY_STRUCT(Outer, inner, label);
// Both must be registered
fory.register_struct<Inner>(1);
diff --git a/docs/guide/cpp/index.md b/docs/guide/cpp/index.md
index fd2cc732a..fefaef00d 100644
--- a/docs/guide/cpp/index.md
+++ b/docs/guide/cpp/index.md
@@ -188,6 +188,24 @@ int main() {
}
```
+### Inherited Fields
+
+To include base-class fields in a derived type, list `FORY_BASE(Base)` inside
+`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields can be
+referenced.
+
+```cpp
+struct Base {
+ int32_t id;
+ FORY_STRUCT(Base, id);
+};
+
+struct Derived : Base {
+ std::string name;
+ FORY_STRUCT(Derived, FORY_BASE(Base), name);
+};
+```
+
## Thread Safety
Apache Fory™ C++ provides two variants for different threading needs:
diff --git a/docs/guide/cpp/row-format.md b/docs/guide/cpp/row-format.md
index a3638ab7c..8c410b7e3 100644
--- a/docs/guide/cpp/row-format.md
+++ b/docs/guide/cpp/row-format.md
@@ -51,16 +51,13 @@ Apache Fory™ Row Format is a binary format optimized for:
using namespace fory::row;
using namespace fory::row::encoder;
-// Define a struct
struct Person {
int32_t id;
std::string name;
float score;
+ FORY_STRUCT(Person, id, name, score);
};
-// Register field metadata (required for row encoding)
-FORY_FIELD_INFO(Person, id, name, score);
-
int main() {
// Create encoder
RowEncoder<Person> encoder;
@@ -94,12 +91,11 @@ 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_STRUCT(Point, x, y);
};
-FORY_FIELD_INFO(Point, x, y);
// Create encoder
RowEncoder<Point> encoder;
@@ -122,14 +118,14 @@ auto row = encoder.GetWriter().ToRow();
struct Address {
std::string city;
std::string country;
+ FORY_STRUCT(Address, city, country);
};
-FORY_FIELD_INFO(Address, city, country);
struct Person {
std::string name;
Address address;
+ FORY_STRUCT(Person, name, address);
};
-FORY_FIELD_INFO(Person, name, address);
// Encode nested struct
RowEncoder<Person> encoder;
@@ -151,8 +147,8 @@ std::string country = address_row->GetString(1);
struct Record {
std::vector<int32_t> values;
std::string label;
+ FORY_STRUCT(Record, values, label);
};
-FORY_FIELD_INFO(Record, values, label);
RowEncoder<Record> encoder;
Record record{{1, 2, 3, 4, 5}, "test"};
@@ -339,7 +335,7 @@ RowEncodeTrait<std::vector<int32_t>>::Type(); // Returns
list(int32())
RowEncodeTrait<std::map<std::string, int32_t>>::Type();
// Returns map(utf8(), int32())
-// Type inference for structs (requires FORY_FIELD_INFO)
+// Type inference for structs (requires FORY_STRUCT)
RowEncodeTrait<Person>::Type(); // Returns struct_({...})
RowEncodeTrait<Person>::Schema(); // Returns schema({...})
```
@@ -494,20 +490,20 @@ 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 |
+| 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_STRUCT) | `struct_({...})` | Variable |
## Related Topics
diff --git a/docs/guide/java/row-format.md b/docs/guide/java/row-format.md
index 134b58ee1..eb58646b3 100644
--- a/docs/guide/java/row-format.md
+++ b/docs/guide/java/row-format.md
@@ -142,19 +142,17 @@ print(foo_row.f4[100000].f1)
struct Bar {
std::string f1;
std::vector<int64_t> f2;
+ FORY_STRUCT(Bar, f1, f2);
};
-FORY_FIELD_INFO(Bar, f1, f2);
-
struct Foo {
int32_t f1;
std::vector<int32_t> f2;
std::map<std::string, int32_t> f3;
std::vector<Bar> f4;
+ FORY_STRUCT(Foo, f1, f2, f3, f4);
};
-FORY_FIELD_INFO(Foo, f1, f2, f3, f4);
-
fory::encoder::RowEncoder<Foo> encoder;
encoder.Encode(foo);
auto row = encoder.GetWriter().ToRow();
diff --git a/docs/guide/python/row-format.md b/docs/guide/python/row-format.md
index c0cc57beb..3fd86f9f3 100644
--- a/docs/guide/python/row-format.md
+++ b/docs/guide/python/row-format.md
@@ -121,19 +121,17 @@ Bar bar2 = barEncoder.fromRow(f4Array.getStruct(20));
// Deserialize 21st Ba
struct Bar {
std::string f1;
std::vector<int64_t> f2;
+ FORY_STRUCT(Bar, f1, f2);
};
-FORY_FIELD_INFO(Bar, f1, f2);
-
struct Foo {
int32_t f1;
std::vector<int32_t> f2;
std::map<std::string, int32_t> f3;
std::vector<Bar> f4;
+ FORY_STRUCT(Foo, f1, f2, f3, f4);
};
-FORY_FIELD_INFO(Foo, f1, f2, f3, f4);
-
fory::encoder::RowEncoder<Foo> encoder;
encoder.Encode(foo);
auto row = encoder.GetWriter().ToRow();
diff --git a/docs/specification/xlang_serialization_spec.md
b/docs/specification/xlang_serialization_spec.md
index 530edbac5..e02ee979c 100644
--- a/docs/specification/xlang_serialization_spec.md
+++ b/docs/specification/xlang_serialization_spec.md
@@ -1636,7 +1636,7 @@ Meta strings are required for enum and struct
serialization (encoding field name
### C++
-- Compile-time reflection via macros (`FORY_STRUCT`, `FORY_FIELD_INFO`)
+- Compile-time reflection via macros (`FORY_STRUCT`)
- Template meta programming for type dispatch and serializer selection
- Uses `std::shared_ptr` for reference tracking
- Compile-time field ordering
diff --git a/examples/cpp/hello_row/README.md b/examples/cpp/hello_row/README.md
index 446cfb4e3..8a6fcb059 100644
--- a/examples/cpp/hello_row/README.md
+++ b/examples/cpp/hello_row/README.md
@@ -107,18 +107,17 @@ This example demonstrates:
## Key Concepts
-### Registering Field Info
+### Registering Struct Metadata
-Use the `FORY_FIELD_INFO` macro to enable automatic encoding:
+Use the `FORY_STRUCT` macro to enable automatic encoding:
```cpp
struct Employee {
std::string name;
int32_t id;
float salary;
+ FORY_STRUCT(Employee, name, id, salary);
};
-
-FORY_FIELD_INFO(Employee, name, id, salary);
```
### Manual Row Writing
diff --git a/examples/cpp/hello_row/main.cc b/examples/cpp/hello_row/main.cc
index 4d888590f..d53fc664b 100644
--- a/examples/cpp/hello_row/main.cc
+++ b/examples/cpp/hello_row/main.cc
@@ -45,20 +45,17 @@ struct Employee {
std::string name;
int32_t id;
float salary;
+ FORY_STRUCT(Employee, name, id, salary);
};
-// Register field info for automatic encoding
-FORY_FIELD_INFO(Employee, name, id, salary);
-
// Define a nested struct
struct Department {
std::string dept_name;
Employee manager;
std::vector<Employee> employees;
+ FORY_STRUCT(Department, dept_name, manager, employees);
};
-FORY_FIELD_INFO(Department, dept_name, manager, employees);
-
int main() {
std::cout << "=== Fory C++ Row Format Example ===" << std::endl << std::endl;
diff --git a/examples/cpp/hello_world/README.md
b/examples/cpp/hello_world/README.md
index 76b494c8a..ce0bf7052 100644
--- a/examples/cpp/hello_world/README.md
+++ b/examples/cpp/hello_world/README.md
@@ -106,9 +106,8 @@ Use the `FORY_STRUCT` macro to register struct fields for
serialization:
struct Point {
int32_t x;
int32_t y;
+ FORY_STRUCT(Point, x, y);
};
-
-FORY_STRUCT(Point, x, y);
```
### Creating a Fory Instance
diff --git a/examples/cpp/hello_world/main.cc b/examples/cpp/hello_world/main.cc
index 875ea82b7..8862ea4c8 100644
--- a/examples/cpp/hello_world/main.cc
+++ b/examples/cpp/hello_world/main.cc
@@ -40,10 +40,9 @@ struct Point {
bool operator==(const Point &other) const {
return x == other.x && y == other.y;
}
-};
-// Register struct fields with Fory using FORY_STRUCT macro
-FORY_STRUCT(Point, x, y);
+ FORY_STRUCT(Point, x, y);
+};
// Define a more complex struct with various field types
struct Person {
@@ -54,9 +53,9 @@ struct Person {
bool operator==(const Person &other) const {
return name == other.name && age == other.age && hobbies == other.hobbies;
}
-};
-FORY_STRUCT(Person, name, age, hobbies);
+ FORY_STRUCT(Person, name, age, hobbies);
+};
// Define a nested struct
struct Team {
@@ -68,9 +67,9 @@ struct Team {
return team_name == other.team_name && members == other.members &&
headquarters == other.headquarters;
}
-};
-FORY_STRUCT(Team, team_name, members, headquarters);
+ FORY_STRUCT(Team, team_name, members, headquarters);
+};
// Define an enum
enum class Status { PENDING, ACTIVE, COMPLETED };
diff --git a/java/fory-format/pom.xml b/java/fory-format/pom.xml
index 7667a651c..19e685b3e 100644
--- a/java/fory-format/pom.xml
+++ b/java/fory-format/pom.xml
@@ -105,6 +105,13 @@
</archive>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <argLine>--add-opens=java.base/java.nio=ALL-UNNAMED</argLine>
+ </configuration>
+ </plugin>
</plugins>
</build>
diff --git a/python/README.md b/python/README.md
index 4f91c06ff..b74b3fd47 100644
--- a/python/README.md
+++ b/python/README.md
@@ -603,19 +603,17 @@ And in C++ with compile-time type information:
struct Bar {
std::string f1;
std::vector<int64_t> f2;
+ FORY_STRUCT(Bar, f1, f2);
};
-FORY_FIELD_INFO(Bar, f1, f2);
-
struct Foo {
int32_t f1;
std::vector<int32_t> f2;
std::map<std::string, int32_t> f3;
std::vector<Bar> f4;
+ FORY_STRUCT(Foo, f1, f2, f3, f4);
};
-FORY_FIELD_INFO(Foo, f1, f2, f3, f4);
-
// Create large dataset
Foo foo;
foo.f1 = 10;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]