diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f8e5d1e8e..a9028cb28d 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 63d109ab78..35b82d6f12 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 b66a3b04ff..42c1e4cf05 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

-| 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 b8fbb77c78..452bce9132 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -384,8 +384,10 @@ struct Cat { std::shared_ptr friend; std::optional name; std::vector 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 2580c5abb1..49298cb862 100644 --- a/compiler/fory_compiler/generators/cpp.py +++ b/compiler/fory_compiler/generators/cpp.py @@ -114,7 +114,6 @@ def generate_header(self) -> GeneratedFile: 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 @@ def generate_header(self) -> GeneratedFile: # 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 @@ def generate_header(self) -> GeneratedFile: self.generate_message_definition( item, [], - struct_macros, enum_macros, union_macros, field_config_macros, @@ -192,10 +188,6 @@ def generate_header(self) -> GeneratedFile: ) lines.append("") - if struct_macros: - lines.extend(struct_macros) - lines.append("") - if namespace: lines.append(f"}} // namespace {namespace}") lines.append("") @@ -396,7 +388,6 @@ def generate_message_definition( 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 @@ def generate_message_definition( self.generate_message_definition( nested_msg, lineage, - struct_macros, enum_macros, union_macros, field_config_macros, @@ -436,7 +426,6 @@ def generate_message_definition( self.generate_union_definition( nested_union, lineage, - struct_macros, body_indent, ) ) @@ -470,12 +459,9 @@ def generate_message_definition( 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 @@ def generate_message_definition( 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 @@ def generate_union_definition( 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 dc74e79192..0680cc3c94 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(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(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`. 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 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 encoder; diff --git a/cpp/fory/encoder/row_encode_trait.h b/cpp/fory/encoder/row_encode_trait.h index 134cf96433..b1facd3c3c 100644 --- a/cpp/fory/encoder/row_encode_trait.h +++ b/cpp/fory/encoder/row_encode_trait.h @@ -192,7 +192,7 @@ struct RowEncodeTrait< template static FieldPtr GetField() { using FieldType = meta::RemoveMemberPointerCVRefT< - std::tuple_element_t>; + std::tuple_element_t>; return field(details::StringViewToString(FieldInfo::Names[I]), RowEncodeTrait::Type()); } @@ -205,9 +205,9 @@ struct RowEncodeTrait< template static void WriteField(V &&visitor, const T &value, RowWriter &writer) { using FieldType = meta::RemoveMemberPointerCVRefT< - std::tuple_element_t>; + std::tuple_element_t>; RowEncodeTrait::Write(std::forward(visitor), - value.*std::get(FieldInfo::Ptrs), + value.*std::get(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 3d153d2e7f..4fd3819082 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::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::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::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::Schema()); std::vector> children; @@ -202,10 +198,9 @@ TEST(RowEncodeTrait, StructInArray) { struct E { int a; std::vector 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 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> a; std::map 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 93355d2ec7..a410ce33a8 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 enc; + + auto &schema = enc.GetSchema(); + ASSERT_EQ(schema.field_names(), (std::vector{"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 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 in_enc; + encoder::RowEncoder 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 x; bool y; + FORY_STRUCT(C, x, y); }; -FORY_FIELD_INFO(C, x, y); - TEST(RowEncoder, SimpleArray) { std::vector 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 ec7dcd3a99..de2fa3b09e 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(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 constexpr auto ForyFieldInfo(const T &) noexcept { - static_assert(AlwaysFalse, - "FORY_FIELD_INFO for type T is expected but not defined"); -} +template struct Identity { + using Type = T; +}; namespace details { +template +using MemberStructInfo = + decltype(T::ForyStructInfo(std::declval>())); + +template +struct HasMemberStructInfo : std::false_type {}; + +template +struct HasMemberStructInfo>> + : std::true_type {}; + +template +using AdlStructInfo = decltype(ForyStructInfo(std::declval>())); + +template +struct HasAdlStructInfo : std::false_type {}; + +template +struct HasAdlStructInfo>> : std::true_type {}; + +template struct TupleWrapper { + T value; +}; + +template constexpr TupleWrapper WrapTuple(const T &value) { + return {value}; +} + +template constexpr const T &UnwrapTuple(const T &value) { + return value; +} + +template +constexpr const T &UnwrapTuple(const TupleWrapper &value) { + return value.value; +} + // it must be able to be executed in compile-time template constexpr bool IsValidFieldInfoImpl(std::index_sequence) { - return IsUnique(FieldInfo::Ptrs)...>::value; + constexpr auto ptrs = FieldInfo::Ptrs(); + return IsUnique(ptrs)...>::value; } } // namespace details @@ -56,41 +88,267 @@ template constexpr bool IsValidFieldInfo() { std::make_index_sequence{}); } +template +struct HasForyStructInfo + : std::bool_constant::value || + details::HasAdlStructInfo::value> {}; + +// decltype(ForyFieldInfo(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 +constexpr auto ForyFieldInfo([[maybe_unused]] const T &value) noexcept { + if constexpr (details::HasMemberStructInfo::value) { + using FieldInfo = decltype(T::ForyStructInfo(Identity{})); + static_assert(IsValidFieldInfo(), + "duplicated fields in FORY_STRUCT arguments are detected"); + return T::ForyStructInfo(Identity{}); + } else if constexpr (details::HasAdlStructInfo::value) { + using FieldInfo = decltype(ForyStructInfo(Identity{})); + static_assert(IsValidFieldInfo(), + "duplicated fields in FORY_STRUCT arguments are detected"); + return ForyStructInfo(Identity{}); + } else { + static_assert(AlwaysFalse, + "FORY_STRUCT for type T is expected but not defined"); + } +} + +constexpr std::array ConcatArrays() { return {}; } + +template +constexpr std::array +ConcatArrays(const std::array &value) { + return value; +} + +template +constexpr std::array +ConcatArrays(const std::array &left, + const std::array &right) { + std::array 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 +constexpr auto ConcatArrays(const std::array &left, + const std::array &right, + const Rest &...rest) { + return ConcatArrays(ConcatArrays(left, right), rest...); +} + +constexpr std::tuple<> ConcatTuples() { return {}; } + +template constexpr Tuple ConcatTuples(const Tuple &tuple) { + return tuple; +} + +template +constexpr auto ConcatTwoTuplesImpl(const Tuple1 &left, const Tuple2 &right, + std::index_sequence, + std::index_sequence) { + return std::tuple{std::get(left)..., std::get(right)...}; +} + +template +constexpr auto ConcatTuples(const Tuple1 &left, const Tuple2 &right) { + return ConcatTwoTuplesImpl( + left, right, std::make_index_sequence>{}, + std::make_index_sequence>{}); +} + +template +constexpr auto ConcatTuples(const Tuple1 &left, const Tuple2 &right, + const Rest &...rest) { + return ConcatTuples(ConcatTuples(left, right), rest...); +} + +template +constexpr auto ConcatArraysFromTupleImpl(const Tuple &tuple, + std::index_sequence) { + return ConcatArrays(std::get(tuple)...); +} + +template +constexpr auto ConcatArraysFromTuple(const Tuple &tuple) { + return ConcatArraysFromTupleImpl( + tuple, std::make_index_sequence>{}); +} + +template +constexpr auto ConcatTuplesFromTupleImpl(const Tuple &tuple, + std::index_sequence) { + return ConcatTuples(details::UnwrapTuple(std::get(tuple))...); +} + +template +constexpr auto ConcatTuplesFromTuple(const Tuple &tuple) { + return ConcatTuplesFromTupleImpl( + tuple, std::make_index_sequence>{}); +} + } // 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()))::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()))::Ptrs()), + +#define FORY_BASE_SIZE_ADD(arg) \ + FORY_PP_IF(FORY_PP_IS_BASE(arg)) \ + (+decltype(::fory::meta::ForyFieldInfo( \ + std::declval()))::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, "it must be a class type"); \ - template struct ForyFieldInfoImpl; \ - template <> struct ForyFieldInfoImpl { \ - 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 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 \ + 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(), std::declval())); \ + 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>(), \ - "duplicated fields in FORY_FIELD_INFO arguments are detected"); \ - static_assert(ForyFieldInfoImpl::Name.data() != nullptr, \ - "ForyFieldInfoImpl name must be available"); \ - static_assert(ForyFieldInfoImpl::Names.size() == \ - ForyFieldInfoImpl::Size, \ - "ForyFieldInfoImpl names size mismatch"); \ - inline constexpr auto ForyFieldInfo(const type &) noexcept { \ - return ForyFieldInfoImpl{}; \ + 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 &) noexcept { \ + return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \ } \ + [[maybe_unused]] inline static constexpr std::true_type ForyStructMarker( \ + const ::fory::meta::Identity &) noexcept { \ + return {}; \ + } + +#define FORY_STRUCT_DETAIL_EMPTY(type, unique_id) \ + static_assert(std::is_class_v, "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 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 (*)(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 &) noexcept { \ + return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \ + } \ + [[maybe_unused]] inline static constexpr std::true_type ForyStructMarker( \ + const ::fory::meta::Identity &) 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 6cb79a9b24..f07bf0067e 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 11379cfb24..f3976fd05d 100644 --- a/cpp/fory/meta/field_test.cc +++ b/cpp/fory/meta/field_test.cc @@ -240,10 +240,9 @@ struct Person { field, 2> nickname; field, 3, ref> parent; field, 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 reviewer; std::shared_ptr parent; std::unique_ptr 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 left; std::shared_ptr 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 771365f11d..79f02eb145 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 f6ac986468..05e72a8d0d 100644 --- a/cpp/fory/row/schema.h +++ b/cpp/fory/row/schema.h @@ -89,7 +89,7 @@ class DataType : public std::enable_shared_from_this { 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 fields() const { return {}; } diff --git a/cpp/fory/serialization/collection_serializer_test.cc b/cpp/fory/serialization/collection_serializer_test.cc index e8dddf5f4d..d664510792 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> animals; + FORY_STRUCT(VectorPolymorphicHolder, animals); }; -FORY_STRUCT(VectorPolymorphicHolder, animals); struct VectorHomogeneousHolder { std::vector> dogs; + FORY_STRUCT(VectorHomogeneousHolder, dogs); }; -FORY_STRUCT(VectorHomogeneousHolder, dogs); namespace { @@ -285,13 +285,13 @@ TEST(CollectionSerializerTest, VectorEmpty) { struct VectorStringHolder { std::vector strings; + FORY_STRUCT(VectorStringHolder, strings); }; -FORY_STRUCT(VectorStringHolder, strings); struct VectorIntHolder { std::vector 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> 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 strings; + FORY_STRUCT(ListStringHolder, strings); }; -FORY_STRUCT(ListStringHolder, strings); struct ListIntHolder { std::list 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 strings; + FORY_STRUCT(DequeStringHolder, strings); }; -FORY_STRUCT(DequeStringHolder, strings); struct DequeIntHolder { std::deque 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 strings; + FORY_STRUCT(ForwardListStringHolder, strings); }; -FORY_STRUCT(ForwardListStringHolder, strings); struct ForwardListIntHolder { std::forward_list 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 c40f4cf8f0..142381e2f2 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, 0> value; fory::field, 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, 0, fory::ref> first; fory::field, 1, fory::ref> second; + FORY_STRUCT(FieldRefTrackingHolder, first, second); }; -FORY_STRUCT(FieldRefTrackingHolder, first, second); // Struct with nullable + ref struct FieldNullableRefHolder { fory::field, 0, fory::nullable, fory::ref> node; + FORY_STRUCT(FieldNullableRefHolder, node); }; -FORY_STRUCT(FieldNullableRefHolder, node); // Struct with not_null + ref struct FieldNotNullRefHolder { fory::field, 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 first; std::shared_ptr 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 required_node; std::shared_ptr 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 left; std::shared_ptr 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 501df1f075..496a8e62eb 100644 --- a/cpp/fory/serialization/fory.h +++ b/cpp/fory/serialization/fory.h @@ -221,8 +221,10 @@ class BaseFory { /// /// Example: /// ```cpp - /// struct MyStruct { int32_t value; }; - /// FORY_STRUCT(MyStruct, value); + /// struct MyStruct { + /// int32_t value; + /// FORY_STRUCT(MyStruct, value); + /// }; /// /// fory.register_struct(1); /// ``` diff --git a/cpp/fory/serialization/map_serializer_test.cc b/cpp/fory/serialization/map_serializer_test.cc index 40caac43af..bf91e939f1 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 bf584bdbda..9b7ee8e985 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 7867776906..cd3ea0480e 100644 --- a/cpp/fory/serialization/serializer_traits.h +++ b/cpp/fory/serialization/serializer_traits.h @@ -216,9 +216,9 @@ inline const nullable_element_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 struct has_fory_field_info : std::false_type {}; @@ -230,7 +230,7 @@ struct has_fory_field_info< template inline constexpr bool has_fory_field_info_v = has_fory_field_info::value; -/// Check if type is serializable (has both FORY_FIELD_INFO and +/// Check if type is serializable (has both FORY_STRUCT and /// SerializationMeta) template 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 d69eaff97e..8c4b03c29d 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 value; + FORY_STRUCT(OptionalIntHolder, value); }; -FORY_STRUCT(OptionalIntHolder, value); struct OptionalSharedHolder { std::optional> value; + FORY_STRUCT(OptionalSharedHolder, value); }; -FORY_STRUCT(OptionalSharedHolder, value); struct SharedPair { std::shared_ptr first; std::shared_ptr second; + FORY_STRUCT(SharedPair, first, second); }; -FORY_STRUCT(SharedPair, first, second); struct UniqueHolder { std::unique_ptr 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 ptr; + FORY_STRUCT(PolymorphicSharedHolder, ptr); }; -FORY_STRUCT(PolymorphicSharedHolder, ptr); struct PolymorphicUniqueHolder { std::unique_ptr 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 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 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, 0, fory::nullable, fory::dynamic> 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 0cabf9476a..5777447cf5 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 11bc7a9e01..efa396e626 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 struct SerializationMeta { static constexpr bool is_serializable = false; static constexpr size_t field_count = 0; }; template -struct SerializationMeta< - T, std::void_t()))>> { +struct SerializationMeta::value>> { static constexpr bool is_serializable = true; static constexpr size_t field_count = decltype(ForyFieldInfo(std::declval()))::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, "it must be a class type"); \ - template struct ForyFieldInfoImpl; \ - template <> struct ForyFieldInfoImpl { \ - static inline constexpr size_t Size = 0; \ - static inline constexpr std::string_view Name = #Type; \ - static inline constexpr std::array Names = {}; \ - static inline constexpr auto Ptrs = std::tuple{}; \ - }; \ - static_assert( \ - fory::meta::IsValidFieldInfo>(), \ - "duplicated fields in FORY_FIELD_INFO arguments are detected"); \ - inline constexpr auto ForyFieldInfo(const Type &) noexcept { \ - return ForyFieldInfoImpl{}; \ - } \ - inline constexpr std::true_type ForyStructMarker(const Type &) noexcept { \ - return {}; \ - } \ - static_assert(static_cast( \ - &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( \ - &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 struct CompileTimeFieldHelpers { using FieldDescriptor = decltype(ForyFieldInfo(std::declval())); 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 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(); const auto field_info = ForyFieldInfo(obj); - const auto field_ptr = std::get(decltype(field_info)::Ptrs); + const auto field_ptr = + std::get(decltype(field_info)::PtrsRef()); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; using FieldType = unwrap_field_t; @@ -1631,7 +1568,8 @@ FORY_ALWAYS_INLINE void write_single_varint_field(const T &obj, Buffer &buffer, using Helpers = CompileTimeFieldHelpers; constexpr size_t original_index = Helpers::sorted_indices[SortedPos]; const auto field_info = ForyFieldInfo(obj); - const auto field_ptr = std::get(decltype(field_info)::Ptrs); + const auto field_ptr = + std::get(decltype(field_info)::PtrsRef()); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; using FieldType = unwrap_field_t; @@ -1673,7 +1611,8 @@ write_single_remaining_field(const T &obj, Buffer &buffer, uint32_t &offset) { using Helpers = CompileTimeFieldHelpers; constexpr size_t original_index = Helpers::sorted_indices[SortedPos]; const auto field_info = ForyFieldInfo(obj); - const auto field_ptr = std::get(decltype(field_info)::Ptrs); + const auto field_ptr = + std::get(decltype(field_info)::PtrsRef()); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; using FieldType = unwrap_field_t; @@ -1963,7 +1902,7 @@ void write_field_at_sorted_position(const T &obj, WriteContext &ctx, using Helpers = CompileTimeFieldHelpers; 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(obj, ctx, field_ptrs, has_generics); } @@ -2154,7 +2093,7 @@ template void read_single_field_by_index(T &obj, ReadContext &ctx) { using Helpers = CompileTimeFieldHelpers; 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(field_ptrs); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; @@ -2339,7 +2278,7 @@ void read_single_field_by_index_compatible(T &obj, ReadContext &ctx, uint32_t remote_type_id) { using Helpers = CompileTimeFieldHelpers; 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(field_ptrs); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; @@ -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(); const auto field_info = ForyFieldInfo(obj); - const auto field_ptr = std::get(decltype(field_info)::Ptrs); + const auto field_ptr = + std::get(decltype(field_info)::PtrsRef()); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; using FieldType = unwrap_field_t; @@ -2676,7 +2616,8 @@ FORY_ALWAYS_INLINE void read_single_varint_field(T &obj, Buffer &buffer, using Helpers = CompileTimeFieldHelpers; constexpr size_t original_index = Helpers::sorted_indices[SortedPos]; const auto field_info = ForyFieldInfo(obj); - const auto field_ptr = std::get(decltype(field_info)::Ptrs); + const auto field_ptr = + std::get(decltype(field_info)::PtrsRef()); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; using FieldType = unwrap_field_t; diff --git a/cpp/fory/serialization/struct_test.cc b/cpp/fory/serialization/struct_test.cc index 2a5882d262..4d5d55cb58 100644 --- a/cpp/fory/serialization/struct_test.cc +++ b/cpp/fory/serialization/struct_test.cc @@ -39,7 +39,8 @@ #include // ============================================================================ -// 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 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 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 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> 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(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); + fory.register_struct(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); @@ -330,6 +396,10 @@ inline void register_all_test_types(Fory &fory) { fory.register_struct(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); + fory.register_struct(type_id++); + fory.register_struct(type_id++); + fory.register_struct(type_id++); + fory.register_struct(type_id++); } template 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 64c3b19b08..8eed7d7a45 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 values; + FORY_STRUCT(VectorHolder, values); }; -FORY_STRUCT(VectorHolder, values); struct TupleHomogeneousHolder { std::tuple values; + FORY_STRUCT(TupleHomogeneousHolder, values); }; -FORY_STRUCT(TupleHomogeneousHolder, values); struct TupleHeterogeneousHolder { std::tuple values; + FORY_STRUCT(TupleHeterogeneousHolder, values); }; -FORY_STRUCT(TupleHeterogeneousHolder, values); struct TupleSingleHolder { std::tuple 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::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 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 efdd891f4b..827243d1fa 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 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 0bf7fe8c58..30196c1deb 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 u8_vec; std::vector 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 1d0a408a64..89688129f4 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 95eaa801e2..f762d6ea87 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 parent; // Non-owning back-reference /// std::vector> children; // Owning references +/// FORY_STRUCT(Node, value, parent, children); /// }; -/// FORY_STRUCT(Node, value, parent, children); /// /// auto parent = std::make_shared(); /// parent->value = 1; diff --git a/cpp/fory/serialization/weak_ptr_serializer_test.cc b/cpp/fory/serialization/weak_ptr_serializer_test.cc index 4da9d5300f..bd34c6dd16 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 weak_ref; + FORY_STRUCT(StructWithWeak, id, weak_ref); }; -FORY_STRUCT(StructWithWeak, id, weak_ref); struct StructWithBothRefs { int32_t id; std::shared_ptr strong_ref; SharedWeak weak_ref; + FORY_STRUCT(StructWithBothRefs, id, strong_ref, weak_ref); }; -FORY_STRUCT(StructWithBothRefs, id, strong_ref, weak_ref); struct MultipleWeakRefsWithOwner { std::shared_ptr owner; SharedWeak weak1; SharedWeak weak2; SharedWeak weak3; + FORY_STRUCT(MultipleWeakRefsWithOwner, owner, weak1, weak2, weak3); }; -FORY_STRUCT(MultipleWeakRefsWithOwner, owner, weak1, weak2, weak3); struct NodeWithParent { int32_t value; SharedWeak parent; std::vector> 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 299ae8e705..f13823268f 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 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 items; bool operator==(const StructWithList &other) const { return items == other.items; } + FORY_STRUCT(StructWithList, items); }; -FORY_STRUCT(StructWithList, items); struct StructWithMap { std::map 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 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> animals; + FORY_STRUCT(AnimalListHolder, animals); }; -FORY_STRUCT(AnimalListHolder, animals); struct AnimalMapHolder { std::map> 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 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 &a, const std::optional &b) { @@ -371,11 +377,6 @@ struct NullableComprehensiveSchemaConsistent { 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 &a, const std::optional &b) { @@ -458,12 +466,6 @@ struct NullableComprehensiveCompatible { 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 894dc27016..58c4649c5d 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 00b11eed6b..a3684c5df2 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 ad76966105..e726ebbc19 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 e45df7e383..3d331450b7 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 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(1); diff --git a/docs/guide/cpp/index.md b/docs/guide/cpp/index.md index fd2cc732a7..fefaef00d6 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 a3638ab7c6..8c410b7e30 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 encoder; @@ -94,12 +91,11 @@ The `RowEncoder` 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 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 encoder; @@ -151,8 +147,8 @@ std::string country = address_row->GetString(1); struct Record { std::vector values; std::string label; + FORY_STRUCT(Record, values, label); }; -FORY_FIELD_INFO(Record, values, label); RowEncoder encoder; Record record{{1, 2, 3, 4, 5}, "test"}; @@ -339,7 +335,7 @@ RowEncodeTrait>::Type(); // Returns list(int32()) RowEncodeTrait>::Type(); // Returns map(utf8(), int32()) -// Type inference for structs (requires FORY_FIELD_INFO) +// Type inference for structs (requires FORY_STRUCT) RowEncodeTrait::Type(); // Returns struct_({...}) RowEncodeTrait::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` | `list(T)` | Variable | -| `std::map` | `map(K,V)` | Variable | -| `std::optional` | 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` | `list(T)` | Variable | +| `std::map` | `map(K,V)` | Variable | +| `std::optional` | 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 134b58ee1f..eb58646b38 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 f2; + FORY_STRUCT(Bar, f1, f2); }; -FORY_FIELD_INFO(Bar, f1, f2); - struct Foo { int32_t f1; std::vector f2; std::map f3; std::vector f4; + FORY_STRUCT(Foo, f1, f2, f3, f4); }; -FORY_FIELD_INFO(Foo, f1, f2, f3, f4); - fory::encoder::RowEncoder 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 c0cc57bebf..3fd86f9f3e 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 f2; + FORY_STRUCT(Bar, f1, f2); }; -FORY_FIELD_INFO(Bar, f1, f2); - struct Foo { int32_t f1; std::vector f2; std::map f3; std::vector f4; + FORY_STRUCT(Foo, f1, f2, f3, f4); }; -FORY_FIELD_INFO(Foo, f1, f2, f3, f4); - fory::encoder::RowEncoder 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 530edbac59..e02ee979c2 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 446cfb4e31..8a6fcb0599 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 4d888590f4..d53fc664bb 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 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 76b494c8a1..ce0bf70524 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 875ea82b74..8862ea4c89 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 7667a651cd..19e685b3e0 100644 --- a/java/fory-format/pom.xml +++ b/java/fory-format/pom.xml @@ -105,6 +105,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens=java.base/java.nio=ALL-UNNAMED + + diff --git a/python/README.md b/python/README.md index 4f91c06ffd..b74b3fd47a 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 f2; + FORY_STRUCT(Bar, f1, f2); }; -FORY_FIELD_INFO(Bar, f1, f2); - struct Foo { int32_t f1; std::vector f2; std::map f3; std::vector f4; + FORY_STRUCT(Foo, f1, f2, f3, f4); }; -FORY_FIELD_INFO(Foo, f1, f2, f3, f4); - // Create large dataset Foo foo; foo.f1 = 10;