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]

Reply via email to