This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 473becb96 feat(compiler): refine generated c++ API (#3221)
473becb96 is described below
commit 473becb963a6ff64e8142f86f5de0006ed660993
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Jan 27 13:39:35 2026 +0800
feat(compiler): refine generated c++ API (#3221)
## Why?
- Avoid exposing const-reference getters for numeric/enum primitives and
align C++ generated APIs with value semantics.
- Shift mutation of strings/bytes/collections/unions to explicit
`mutable_*` accessors to prevent expensive copies and clarify intent.
## What does this PR do?
- Make the C++ generator to return numeric/enum fields by value, add
`mutable_*` accessors for strings/bytes/collections/unions, and tailor
setters (string forwarding; no setters for bytes/collections/unions).
- Update C++ IDL integration tests to use the new `mutable_*` accessors
for collections, bytes, and unions.
## Related issues
#3099
#2906
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
compiler/fory_compiler/generators/cpp.py | 101 +++++++++++++++++++++++++++++--
integration_tests/idl_tests/cpp/main.cc | 52 ++++++++--------
2 files changed, 123 insertions(+), 30 deletions(-)
diff --git a/compiler/fory_compiler/generators/cpp.py
b/compiler/fory_compiler/generators/cpp.py
index c69a9647c..25c900ca9 100644
--- a/compiler/fory_compiler/generators/cpp.py
+++ b/compiler/fory_compiler/generators/cpp.py
@@ -66,6 +66,26 @@ class CppGenerator(BaseGenerator):
PrimitiveKind.DATE: "fory::serialization::Date",
PrimitiveKind.TIMESTAMP: "fory::serialization::Timestamp",
}
+ NUMERIC_PRIMITIVES = {
+ PrimitiveKind.BOOL,
+ PrimitiveKind.INT8,
+ PrimitiveKind.INT16,
+ PrimitiveKind.INT32,
+ PrimitiveKind.VARINT32,
+ PrimitiveKind.INT64,
+ PrimitiveKind.VARINT64,
+ PrimitiveKind.TAGGED_INT64,
+ PrimitiveKind.UINT8,
+ PrimitiveKind.UINT16,
+ PrimitiveKind.UINT32,
+ PrimitiveKind.VAR_UINT32,
+ PrimitiveKind.UINT64,
+ PrimitiveKind.VAR_UINT64,
+ PrimitiveKind.TAGGED_UINT64,
+ PrimitiveKind.FLOAT16,
+ PrimitiveKind.FLOAT32,
+ PrimitiveKind.FLOAT64,
+ }
def generate(self) -> List[GeneratedFile]:
"""Generate C++ files for the schema."""
@@ -422,6 +442,22 @@ class CppGenerator(BaseGenerator):
resolved = self.resolve_named_type(field_type.name, parent_stack)
return isinstance(resolved, Message)
+ def is_union_type(
+ self, field_type: FieldType, parent_stack: Optional[List[Message]]
+ ) -> bool:
+ if not isinstance(field_type, NamedType):
+ return False
+ resolved = self.resolve_named_type(field_type.name, parent_stack)
+ return isinstance(resolved, Union)
+
+ def is_enum_type(
+ self, field_type: FieldType, parent_stack: Optional[List[Message]]
+ ) -> bool:
+ if not isinstance(field_type, NamedType):
+ return False
+ resolved = self.resolve_named_type(field_type.name, parent_stack)
+ return isinstance(resolved, Enum)
+
def get_field_member_name(self, field: Field) -> str:
return f"{self.to_snake_case(field.name)}_"
@@ -469,6 +505,21 @@ class CppGenerator(BaseGenerator):
)
return f"{member_name} == {other_member}"
+ def is_numeric_field(self, field: Field) -> bool:
+ if not isinstance(field.field_type, PrimitiveType):
+ return False
+ return field.field_type.kind in self.NUMERIC_PRIMITIVES
+
+ def is_string_field(self, field: Field) -> bool:
+ return isinstance(field.field_type, PrimitiveType) and (
+ field.field_type.kind == PrimitiveKind.STRING
+ )
+
+ def is_bytes_field(self, field: Field) -> bool:
+ return isinstance(field.field_type, PrimitiveType) and (
+ field.field_type.kind == PrimitiveKind.BYTES
+ )
+
def generate_field_accessors(
self, field: Field, parent_stack: Optional[List[Message]], indent: str
) -> List[str]:
@@ -476,6 +527,14 @@ class CppGenerator(BaseGenerator):
field_name = self.to_snake_case(field.name)
member_name = self.get_field_member_name(field)
value_type = self.get_field_value_type(field, parent_stack)
+ is_union = self.is_union_type(field.field_type, parent_stack)
+ is_enum = self.is_enum_type(field.field_type, parent_stack)
+ is_collection = isinstance(field.field_type, (ListType, MapType))
+ is_bytes = self.is_bytes_field(field)
+ is_string = self.is_string_field(field)
+ needs_mutable = is_string or is_collection or is_bytes or is_union
+ no_setter = is_collection or is_bytes or is_union
+ value_getter = self.is_numeric_field(field) or is_enum
if self.is_message_type(field.field_type, parent_stack):
lines.append(f"{indent}bool has_{field_name}() const {{")
@@ -506,16 +565,48 @@ class CppGenerator(BaseGenerator):
lines.append(f"{indent}}}")
lines.append("")
- lines.append(f"{indent}const {value_type}& {field_name}() const {{")
+ if value_getter:
+ lines.append(f"{indent}{value_type} {field_name}() const {{")
+ else:
+ lines.append(f"{indent}const {value_type}& {field_name}() const
{{")
if field.optional:
lines.append(f"{indent} return *{member_name};")
else:
lines.append(f"{indent} return {member_name};")
lines.append(f"{indent}}}")
- lines.append("")
- lines.append(f"{indent}void set_{field_name}({value_type} value) {{")
- lines.append(f"{indent} {member_name} = std::move(value);")
- lines.append(f"{indent}}}")
+
+ if needs_mutable:
+ lines.append("")
+ lines.append(f"{indent}{value_type}* mutable_{field_name}() {{")
+ if field.optional:
+ lines.append(f"{indent} if (!{member_name}) {{")
+ lines.append(f"{indent} {member_name}.emplace();")
+ lines.append(f"{indent} }}")
+ lines.append(f"{indent} return &{member_name}.value();")
+ else:
+ lines.append(f"{indent} return &{member_name};")
+ lines.append(f"{indent}}}")
+
+ if not no_setter:
+ lines.append("")
+ if is_string:
+ lines.append(f"{indent}template <class Arg, class... Args>")
+ lines.append(
+ f"{indent}void set_{field_name}(Arg&& arg, Args&&... args)
{{"
+ )
+ if field.optional:
+ lines.append(
+ f"{indent}
{member_name}.emplace(std::forward<Arg>(arg), std::forward<Args>(args)...);"
+ )
+ else:
+ lines.append(
+ f"{indent} {member_name} =
{value_type}(std::forward<Arg>(arg), std::forward<Args>(args)...);"
+ )
+ lines.append(f"{indent}}}")
+ else:
+ lines.append(f"{indent}void set_{field_name}({value_type}
value) {{")
+ lines.append(f"{indent} {member_name} = std::move(value);")
+ lines.append(f"{indent}}}")
if field.optional:
lines.append("")
diff --git a/integration_tests/idl_tests/cpp/main.cc
b/integration_tests/idl_tests/cpp/main.cc
index f0e65955c..d2fbb8967 100644
--- a/integration_tests/idl_tests/cpp/main.cc
+++ b/integration_tests/idl_tests/cpp/main.cc
@@ -84,22 +84,22 @@ fory::Result<void, fory::Error> RunRoundTrip() {
person.set_name("Alice");
person.set_id(123);
person.set_email("[email protected]");
- person.set_tags({"friend", "colleague"});
- person.set_scores({{"math", 100}, {"science", 98}});
+ *person.mutable_tags() = {"friend", "colleague"};
+ *person.mutable_scores() = {{"math", 100}, {"science", 98}};
person.set_salary(120000.5);
- person.set_phones({mobile, work});
+ *person.mutable_phones() = {mobile, work};
addressbook::Dog dog;
dog.set_name("Rex");
dog.set_bark_volume(5);
- person.set_pet(addressbook::Animal::dog(dog));
+ *person.mutable_pet() = addressbook::Animal::dog(dog);
addressbook::Cat cat;
cat.set_name("Mimi");
cat.set_lives(9);
- person.set_pet(addressbook::Animal::cat(cat));
+ *person.mutable_pet() = addressbook::Animal::cat(cat);
addressbook::AddressBook book;
- book.set_people({person});
- book.set_people_by_name({{person.name(), person}});
+ *book.mutable_people() = {person};
+ *book.mutable_people_by_name() = {{person.name(), person}};
FORY_TRY(bytes, fory.serialize(book));
FORY_TRY(roundtrip, fory.deserialize<addressbook::AddressBook>(bytes.data(),
@@ -128,9 +128,9 @@ fory::Result<void, fory::Error> RunRoundTrip() {
types.set_tagged_uint64_value(2222222222ULL);
types.set_float32_value(2.5F);
types.set_float64_value(3.5);
- types.set_contact(addressbook::PrimitiveTypes::Contact::email(
- std::string("[email protected]")));
- types.set_contact(addressbook::PrimitiveTypes::Contact::phone(12345));
+ *types.mutable_contact() =
+ addressbook::PrimitiveTypes::Contact::email("[email protected]");
+ *types.mutable_contact() =
addressbook::PrimitiveTypes::Contact::phone(12345);
FORY_TRY(primitive_bytes, fory.serialize(types));
FORY_TRY(primitive_roundtrip,
@@ -153,8 +153,9 @@ fory::Result<void, fory::Error> RunRoundTrip() {
monster_value.set_hp(80);
monster_value.set_name("Orc");
monster_value.set_friendly(true);
- monster_value.set_inventory({static_cast<uint8_t>(1),
static_cast<uint8_t>(2),
- static_cast<uint8_t>(3)});
+ *monster_value.mutable_inventory() = {static_cast<uint8_t>(1),
+ static_cast<uint8_t>(2),
+ static_cast<uint8_t>(3)};
monster_value.set_color(monster::Color::Blue);
FORY_TRY(monster_bytes, fory.serialize(monster_value));
@@ -169,9 +170,9 @@ fory::Result<void, fory::Error> RunRoundTrip() {
complex_fbs::Container container;
container.set_id(9876543210ULL);
container.set_status(complex_fbs::Status::STARTED);
- container.set_bytes(
- {static_cast<int8_t>(1), static_cast<int8_t>(2),
static_cast<int8_t>(3)});
- container.set_numbers({10, 20, 30});
+ *container.mutable_bytes() = {static_cast<int8_t>(1), static_cast<int8_t>(2),
+ static_cast<int8_t>(3)};
+ *container.mutable_numbers() = {10, 20, 30};
auto *scalars = container.mutable_scalars();
scalars->set_b(-8);
scalars->set_ub(200);
@@ -184,14 +185,14 @@ fory::Result<void, fory::Error> RunRoundTrip() {
scalars->set_f(1.5F);
scalars->set_d(2.5);
scalars->set_ok(true);
- container.set_names({"alpha", "beta"});
- container.set_flags({true, false});
+ *container.mutable_names() = {"alpha", "beta"};
+ *container.mutable_flags() = {true, false};
complex_fbs::Note note;
note.set_text("alpha");
- container.set_payload(complex_fbs::Payload::note(note));
+ *container.mutable_payload() = complex_fbs::Payload::note(note);
complex_fbs::Metric metric;
metric.set_value(42.0);
- container.set_payload(complex_fbs::Payload::metric(metric));
+ *container.mutable_payload() = complex_fbs::Payload::metric(metric);
FORY_TRY(container_bytes, fory.serialize(container));
FORY_TRY(container_roundtrip,
@@ -226,18 +227,19 @@ fory::Result<void, fory::Error> RunRoundTrip() {
all_types.set_float32_value(2.5F);
all_types.set_float64_value(3.5);
all_types.set_string_value("optional");
- all_types.set_bytes_value({static_cast<uint8_t>(1), static_cast<uint8_t>(2),
- static_cast<uint8_t>(3)});
+ *all_types.mutable_bytes_value() = {static_cast<uint8_t>(1),
+ static_cast<uint8_t>(2),
+ static_cast<uint8_t>(3)};
all_types.set_date_value(fory::serialization::Date(19724));
all_types.set_timestamp_value(
fory::serialization::Timestamp(std::chrono::seconds(1704164645)));
- all_types.set_int32_list({1, 2, 3});
- all_types.set_string_list({"alpha", "beta"});
- all_types.set_int64_map({{"alpha", 10}, {"beta", 20}});
+ *all_types.mutable_int32_list() = {1, 2, 3};
+ *all_types.mutable_string_list() = {"alpha", "beta"};
+ *all_types.mutable_int64_map() = {{"alpha", 10}, {"beta", 20}};
optional_types::OptionalHolder holder;
*holder.mutable_all_types() = all_types;
- holder.set_choice(optional_types::OptionalUnion::note("optional"));
+ *holder.mutable_choice() = optional_types::OptionalUnion::note("optional");
FORY_TRY(optional_bytes, fory.serialize(holder));
FORY_TRY(optional_roundtrip,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]