Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c443ce4
make FORY macro to support access private fields
chaokunyang Jan 23, 2026
19e7f19
unify FORY_FIELD_INFO with FORY_STRUCT macro
chaokunyang Jan 23, 2026
087a5fd
add FORY_STRUCT_EXTERNAL macro
chaokunyang Jan 23, 2026
01ba958
update compiler for cpp
chaokunyang Jan 23, 2026
aaa5e18
misc fix
chaokunyang Jan 23, 2026
0d34c64
fix: update C++ hello_world FORY_STRUCT usage and fix java format tests
chaokunyang Jan 23, 2026
8980485
fix(cpp): capture iteration count in pool test
chaokunyang Jan 23, 2026
405b7f2
fix(cpp): adjust polymorphic map test fields
chaokunyang Jan 23, 2026
32656ce
add base class support
chaokunyang Jan 23, 2026
cadac2c
fix(cpp): handle base fields in struct macro
chaokunyang Jan 23, 2026
3951e1b
update code
chaokunyang Jan 23, 2026
a527cea
update doc and tests
chaokunyang Jan 23, 2026
eb82524
fix(cpp): use FORY_BASE for derived structs
chaokunyang Jan 23, 2026
27e78b6
fix(cpp): stabilize field pointer tuples
chaokunyang Jan 23, 2026
0a94fa2
Merge remote-tracking branch 'asf/main' into support_private_fields_o…
chaokunyang Jan 23, 2026
425592e
generate FORY_STRUCT in class body
chaokunyang Jan 24, 2026
d9ab305
upload bazel log
chaokunyang Jan 24, 2026
825fde2
refactor FORY_STRCUT macro
chaokunyang Jan 25, 2026
f002aab
fix(cpp): fix FORY_STRUCT metadata lookup
chaokunyang Jan 25, 2026
6b0edce
test(cpp): move FORY_STRUCT to namespace scope
chaokunyang Jan 25, 2026
1684468
fix(cpp): defer FORY_STRUCT field ptr evaluation
chaokunyang Jan 25, 2026
85de4e4
add Ptrs static constexpr func notes
chaokunyang Jan 25, 2026
4463d91
fix(cpp): silence unused parameter warning
chaokunyang Jan 25, 2026
9bd208f
perf(cpp): add cached PtrsRef accessor
chaokunyang Jan 25, 2026
1d980c5
update benchmark report
chaokunyang Jan 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,4 @@ examples/cpp/cmake_example/build
**/benchmark_*.png
**/results/
benchmarks/**/report/
ignored/**
16 changes: 8 additions & 8 deletions benchmarks/cpp_benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ Note: Protobuf is fetched automatically via CMake FetchContent, so no manual ins
<img src="../../docs/benchmarks/cpp/throughput.png" width="90%">
</p>

| Datatype | Operation | Fory TPS | Protobuf TPS | Faster |
| ------------ | ----------- | ---------- | ------------ | ----------- |
| Mediacontent | Serialize | 11,319,876 | 1,181,595 | Fory (9.6x) |
| Mediacontent | Deserialize | 2,729,388 | 835,956 | Fory (3.3x) |
| Sample | Serialize | 16,899,403 | 10,575,760 | Fory (1.6x) |
| Sample | Deserialize | 3,079,241 | 1,450,789 | Fory (2.1x) |
| Struct | Serialize | 43,184,198 | 29,359,454 | Fory (1.5x) |
| Struct | Deserialize | 54,599,691 | 38,796,674 | Fory (1.4x) |
| Datatype | Operation | Fory TPS | Protobuf TPS | Faster |
| ------------ | ----------- | --------- | ------------ | ----------- |
| Mediacontent | Serialize | 2,254,915 | 504,410 | Fory (4.5x) |
| Mediacontent | Deserialize | 741,303 | 396,013 | Fory (1.9x) |
| Sample | Serialize | 4,248,973 | 3,229,102 | Fory (1.3x) |
| Sample | Deserialize | 935,709 | 715,837 | Fory (1.3x) |
| Struct | Serialize | 9,143,618 | 5,881,005 | Fory (1.6x) |
| Struct | Deserialize | 7,746,787 | 6,202,164 | Fory (1.2x) |

## Quick Start

Expand Down
4 changes: 3 additions & 1 deletion compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,10 @@ struct Cat {
std::shared_ptr<Dog> friend;
std::optional<std::string> name;
std::vector<std::string> tags;
int32_t scores;
int32_t lives;
FORY_STRUCT(Cat, friend, name, tags, scores, lives);
};
FORY_STRUCT(Cat, friend, name, tags, scores, lives);
```

## CLI Reference
Expand Down
24 changes: 7 additions & 17 deletions compiler/fory_compiler/generators/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -173,17 +172,14 @@ 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
lines.extend(
self.generate_message_definition(
item,
[],
struct_macros,
enum_macros,
union_macros,
field_config_macros,
Expand All @@ -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("")
Expand Down Expand Up @@ -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],
Expand All @@ -422,7 +413,6 @@ def generate_message_definition(
self.generate_message_definition(
nested_msg,
lineage,
struct_macros,
enum_macros,
union_macros,
field_config_macros,
Expand All @@ -436,7 +426,6 @@ def generate_message_definition(
self.generate_union_definition(
nested_union,
lineage,
struct_macros,
body_indent,
)
)
Expand Down Expand Up @@ -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
)
Expand All @@ -484,16 +470,20 @@ 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

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."""
Expand Down
49 changes: 45 additions & 4 deletions cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -116,6 +120,43 @@ auto bytes = fory->serialize(person).value();
auto decoded = fory->deserialize<Person>(bytes).value();
```

### 1.1 External/Third-Party Types

For third-party types where you cannot modify the class definition, use
`FORY_STRUCT` at namespace scope. This works for public fields only.

```cpp
namespace thirdparty {
struct Foo {
int32_t id;
std::string name;
};

FORY_STRUCT(Foo, id, name);
} // namespace thirdparty

auto fory = apache::fory::ForyBuilder().build();
fory->register_struct<thirdparty::Foo>(1);
```

### 1.2 Inherited Fields

To include base-class fields in a derived type, use `FORY_BASE(Base)` inside
`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields can be
referenced.

```cpp
struct Base {
int32_t id;
FORY_STRUCT(Base, id);
};

struct Derived : Base {
std::string name;
FORY_STRUCT(Derived, FORY_BASE(Base), name);
};
```

### 2. Shared References

Apache Fory™ automatically tracks and preserves reference identity for shared objects using `std::shared_ptr<T>`. When the same object is referenced multiple times, Fory serializes it only once and uses reference IDs for subsequent occurrences.
Expand Down Expand Up @@ -230,7 +271,7 @@ struct UserProfile {
std::string email;
std::vector<int32_t> scores;
bool is_active;
FORY_FIELD_INFO(UserProfile, id, username, email, scores, is_active);
FORY_STRUCT(UserProfile, id, username, email, scores, is_active);
};

apache::fory::RowEncoder<UserProfile> encoder;
Expand Down
6 changes: 3 additions & 3 deletions cpp/fory/encoder/row_encode_trait.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ struct RowEncodeTrait<

template <size_t I> static FieldPtr GetField() {
using FieldType = meta::RemoveMemberPointerCVRefT<
std::tuple_element_t<I, decltype(FieldInfo::Ptrs)>>;
std::tuple_element_t<I, decltype(FieldInfo::Ptrs())>>;
return field(details::StringViewToString(FieldInfo::Names[I]),
RowEncodeTrait<FieldType>::Type());
}
Expand All @@ -205,9 +205,9 @@ struct RowEncodeTrait<
template <size_t I, typename V>
static void WriteField(V &&visitor, const T &value, RowWriter &writer) {
using FieldType = meta::RemoveMemberPointerCVRefT<
std::tuple_element_t<I, decltype(FieldInfo::Ptrs)>>;
std::tuple_element_t<I, decltype(FieldInfo::Ptrs())>>;
RowEncodeTrait<FieldType>::Write(std::forward<V>(visitor),
value.*std::get<I>(FieldInfo::Ptrs),
value.*std::get<I>(FieldInfo::PtrsRef()),
writer, I);
}

Expand Down
21 changes: 7 additions & 14 deletions cpp/fory/encoder/row_encode_trait_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ struct A {
int x;
float y;
bool z;
FORY_STRUCT(A, x, y, z);
};

FORY_FIELD_INFO(A, x, y, z);

TEST(RowEncodeTrait, Basic) {
auto field_vector = encoder::RowEncodeTrait<A>::FieldVector();

Expand Down Expand Up @@ -67,10 +66,9 @@ TEST(RowEncodeTrait, Basic) {
struct B {
int num;
std::string str;
FORY_STRUCT(B, num, str);
};

FORY_FIELD_INFO(B, num, str);

TEST(RowEncodeTrait, String) {
RowWriter writer(encoder::RowEncodeTrait<B>::Schema());
writer.Reset();
Expand All @@ -89,10 +87,9 @@ struct C {
const int a;
volatile float b;
bool c;
FORY_STRUCT(C, a, b, c);
};

FORY_FIELD_INFO(C, a, b, c);

TEST(RowEncodeTrait, Const) {
RowWriter writer(encoder::RowEncodeTrait<C>::Schema());
writer.Reset();
Expand All @@ -110,10 +107,9 @@ struct D {
int x;
A y;
B z;
FORY_STRUCT(D, x, y, z);
};

FORY_FIELD_INFO(D, x, y, z);

TEST(RowEncodeTrait, NestedStruct) {
RowWriter writer(encoder::RowEncodeTrait<D>::Schema());
std::vector<std::unique_ptr<RowWriter>> children;
Expand Down Expand Up @@ -202,10 +198,9 @@ TEST(RowEncodeTrait, StructInArray) {
struct E {
int a;
std::vector<int> b;
FORY_STRUCT(E, a, b);
};

FORY_FIELD_INFO(E, a, b);

TEST(RowEncodeTrait, ArrayInStruct) {
E e{233, {10, 20, 30}};

Expand Down Expand Up @@ -256,10 +251,9 @@ struct F {
bool a;
std::optional<int> b;
int c;
FORY_STRUCT(F, a, b, c);
};

FORY_FIELD_INFO(F, a, b, c);

TEST(RowEncodeTrait, Optional) {
F x{false, 233, 111}, y{true, std::nullopt, 222};

Expand Down Expand Up @@ -301,10 +295,9 @@ TEST(RowEncodeTrait, Optional) {
struct G {
std::map<int, std::map<int, int>> a;
std::map<std::string, A> b;
FORY_STRUCT(G, a, b);
};

FORY_FIELD_INFO(G, a, b);

TEST(RowEncodeTrait, Map) {
G v{{{1, {{3, 4}, {5, 6}}}, {2, {{7, 8}, {9, 10}, {11, 12}}}},
{{"a", A{1, 1.1, true}}, {"b", A{2, 3.3, false}}}};
Expand Down
Loading
Loading