Skip to content

Commit c3cdacf

Browse files
CopilotJens-G
authored andcommitted
cpp: add template_streamop generation with runtime/compiler test coverage
Add template_streamop support in the C++ generator so generated operator<< and printTo can target generic stream-like output types. Keep default behavior unchanged when the option is not set (std::ostream signatures remain). Add compiler/runtime coverage for template generation, friend declaration correctness, enums, and collection printing. default: ```cpp std::ostream& operator<<(std::ostream& out, const SimpleStruct& obj); class SimpleStruct { public: void printTo(std::ostream& out) const; }; ``` with `template_streamop`: ```cpp template <typename OStream_> OStream_& operator<<(OStream_& out, const SimpleStruct& obj); class SimpleStruct { public: template <typename OStream_> void printTo(OStream_& out) const; }; ```
1 parent 0b68228 commit c3cdacf

File tree

9 files changed

+1078
-34
lines changed

9 files changed

+1078
-34
lines changed

compiler/cpp/src/thrift/generate/t_cpp_generator.cc

Lines changed: 155 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class t_cpp_generator : public t_oop_generator {
6666
gen_templates_only_ = false;
6767
gen_moveable_ = false;
6868
gen_forward_setter_ = false;
69+
gen_template_streamop_ = false;
6970
gen_no_ostream_operators_ = false;
7071
gen_no_skeleton_ = false;
7172
gen_no_constructors_ = false;
@@ -96,6 +97,8 @@ class t_cpp_generator : public t_oop_generator {
9697
}
9798
} else if ( iter->first.compare("no_ostream_operators") == 0) {
9899
gen_no_ostream_operators_ = true;
100+
} else if ( iter->first.compare("template_streamop") == 0) {
101+
gen_template_streamop_ = true;
99102
} else if ( iter->first.compare("no_skeleton") == 0) {
100103
gen_no_skeleton_ = true;
101104
} else if ( iter->first.compare("no_constructors") == 0) {
@@ -130,6 +133,8 @@ class t_cpp_generator : public t_oop_generator {
130133
void generate_enum_ostream_operator(std::ostream& out, t_enum* tenum);
131134
void generate_enum_to_string_helper_function_decl(std::ostream& out, t_enum* tenum);
132135
void generate_enum_to_string_helper_function(std::ostream& out, t_enum* tenum);
136+
void generate_enum_printto_helper_function_decl(std::ostream& out, t_enum* tenum);
137+
void generate_enum_printto_helper_function(std::ostream& out, t_enum* tenum);
133138
void generate_forward_declaration(t_struct* tstruct) override;
134139
void generate_struct(t_struct* tstruct) override { generate_cpp_struct(tstruct, false); }
135140
void generate_xception(t_struct* txception) override { generate_cpp_struct(txception, true); }
@@ -371,6 +376,11 @@ class t_cpp_generator : public t_oop_generator {
371376
*/
372377
bool gen_forward_setter_;
373378

379+
/**
380+
* True if we should generate operator<< and printTo with generic stream type template.
381+
*/
382+
bool gen_template_streamop_;
383+
374384
/**
375385
* True if we should generate ostream definitions
376386
*/
@@ -462,7 +472,7 @@ void t_cpp_generator::init_generator() {
462472
string f_types_impl_name = get_out_dir() + program_name_ + "_types.cpp";
463473
f_types_impl_.open(f_types_impl_name.c_str());
464474

465-
if (gen_templates_ || gen_forward_setter_) {
475+
if (gen_templates_ || gen_forward_setter_ || gen_template_streamop_) {
466476
// If we don't open the stream, it appears to just discard data,
467477
// which is fine.
468478
string f_types_tcc_name = get_out_dir() + program_name_ + "_types.tcc";
@@ -529,6 +539,12 @@ void t_cpp_generator::init_generator() {
529539
f_types_impl_ << "#include <ostream>" << '\n' << '\n';
530540
f_types_impl_ << "#include <thrift/TToString.h>" << '\n' << '\n';
531541

542+
// For template_streamop, we need TPrintTo.h in the .tcc file for direct streaming
543+
// TPrintTo avoids the overhead of to_string which uses ostringstream internally
544+
if (gen_template_streamop_) {
545+
f_types_tcc_ << "#include <thrift/TPrintTo.h>" << '\n' << '\n';
546+
}
547+
532548
// Open namespace
533549
ns_open_ = namespace_open(program_->get_namespace("cpp"));
534550
ns_close_ = namespace_close(program_->get_namespace("cpp"));
@@ -552,7 +568,7 @@ void t_cpp_generator::close_generator() {
552568
// Include the types.tcc file from the types header file,
553569
// so clients don't have to explicitly include the tcc file.
554570
// TODO(simpkins): Make this a separate option.
555-
if (gen_templates_ || gen_forward_setter_) {
571+
if (gen_templates_ || gen_forward_setter_ || gen_template_streamop_) {
556572
f_types_ << "#include \"" << get_include_prefix(*get_program()) << program_name_
557573
<< "_types.tcc\"" << '\n' << '\n';
558574
}
@@ -680,6 +696,12 @@ void t_cpp_generator::generate_enum(t_enum* tenum) {
680696
generate_enum_to_string_helper_function_decl(f_types_, tenum);
681697
generate_enum_to_string_helper_function(f_types_impl_, tenum);
682698

699+
// Generate template printTo specialization for enums when template_streamop is enabled
700+
if (gen_template_streamop_) {
701+
generate_enum_printto_helper_function_decl(f_types_, tenum);
702+
generate_enum_printto_helper_function(f_types_tcc_, tenum);
703+
}
704+
683705
has_members_ = true;
684706
}
685707

@@ -777,6 +799,52 @@ void t_cpp_generator::generate_enum_to_string_helper_function(std::ostream& out,
777799
}
778800
}
779801

802+
void t_cpp_generator::generate_enum_printto_helper_function_decl(std::ostream& out, t_enum* tenum) {
803+
out << "template <typename OStream_>" << '\n';
804+
out << "void printTo(OStream_& out, const ";
805+
if (gen_pure_enums_) {
806+
out << tenum->get_name();
807+
} else {
808+
out << tenum->get_name() << "::type&";
809+
}
810+
out << " val);" << '\n';
811+
out << '\n';
812+
}
813+
814+
void t_cpp_generator::generate_enum_printto_helper_function(std::ostream& out, t_enum* tenum) {
815+
if (!has_custom_ostream(tenum)) {
816+
out << "template <typename OStream_>" << '\n';
817+
out << "void printTo(OStream_& out, const ";
818+
if (gen_pure_enums_) {
819+
out << tenum->get_name();
820+
} else {
821+
out << tenum->get_name() << "::type&";
822+
}
823+
out << " val) ";
824+
scope_up(out);
825+
826+
out << indent() << "std::map<int, const char*>::const_iterator it = _"
827+
<< tenum->get_name() << "_VALUES_TO_NAMES.find(";
828+
if (gen_enum_class_) {
829+
out << "static_cast<int>(val));" << '\n';
830+
} else {
831+
out << "val);" << '\n';
832+
}
833+
out << indent() << "if (it != _" << tenum->get_name() << "_VALUES_TO_NAMES.end()) {" << '\n';
834+
indent_up();
835+
out << indent() << "out << it->second;" << '\n';
836+
indent_down();
837+
out << indent() << "} else {" << '\n';
838+
indent_up();
839+
out << indent() << "out << static_cast<int>(val);" << '\n';
840+
indent_down();
841+
out << indent() << "}" << '\n';
842+
843+
scope_down(out);
844+
out << '\n';
845+
}
846+
}
847+
780848
/**
781849
* Generates a class that holds all the constants.
782850
*/
@@ -1003,7 +1071,9 @@ void t_cpp_generator::generate_cpp_struct(t_struct* tstruct, bool is_exception)
10031071
}
10041072

10051073
if (!has_custom_ostream(tstruct)) {
1006-
generate_struct_print_method(f_types_impl_, tstruct);
1074+
// When template_streamop is enabled, printTo implementation goes to .tcc file
1075+
std::ostream& print_method_out = (gen_template_streamop_ ? f_types_tcc_ : f_types_impl_);
1076+
generate_struct_print_method(print_method_out, tstruct);
10071077
}
10081078

10091079
if (is_exception) {
@@ -1481,7 +1551,8 @@ void t_cpp_generator::generate_struct_declaration(ostream& out,
14811551

14821552
if (is_user_struct && !has_custom_ostream(tstruct)) {
14831553
out << indent();
1484-
if (!gen_templates_) out << "virtual ";
1554+
// Template methods cannot be virtual, so skip virtual keyword when using template_streamop
1555+
if (!gen_templates_ && !gen_template_streamop_) out << "virtual ";
14851556
generate_struct_print_method_decl(out, nullptr);
14861557
out << ";" << '\n';
14871558
}
@@ -1532,7 +1603,9 @@ void t_cpp_generator::generate_struct_declaration(ostream& out,
15321603
// When private_optional is enabled, optional members may be private.
15331604
// The generated namespace-scope operator<< needs friend access.
15341605
if (is_user_struct && gen_private_optional_) {
1535-
indent(out) << "friend ";
1606+
if (!gen_template_streamop_) {
1607+
indent(out) << "friend ";
1608+
}
15361609
generate_struct_ostream_operator_decl(out, tstruct);
15371610
}
15381611

@@ -1610,7 +1683,9 @@ void t_cpp_generator::generate_struct_definition(ostream& out,
16101683
}
16111684
}
16121685
if (is_user_struct) {
1613-
generate_struct_ostream_operator(out, tstruct);
1686+
// When template_streamop is enabled, operator<< implementation goes to .tcc file
1687+
std::ostream& ostream_op_out = (gen_template_streamop_ ? f_types_tcc_ : out);
1688+
generate_struct_ostream_operator(ostream_op_out, tstruct);
16141689
}
16151690
out << '\n';
16161691
}
@@ -1979,18 +2054,35 @@ void t_cpp_generator::generate_struct_swap_decl(std::ostream& out, t_struct* tst
19792054
}
19802055

19812056
void t_cpp_generator::generate_struct_ostream_operator_decl(std::ostream& out, t_struct* tstruct) {
1982-
out << "std::ostream& operator<<(std::ostream& out, const "
1983-
<< tstruct->get_name()
1984-
<< "& obj);" << '\n';
2057+
if (gen_template_streamop_) {
2058+
out << "template <typename OStream_>" << '\n';
2059+
if (gen_private_optional_) {
2060+
out << indent() << "friend ";
2061+
}
2062+
out << "OStream_& operator<<(OStream_& out, const "
2063+
<< tstruct->get_name()
2064+
<< "& obj);" << '\n';
2065+
} else {
2066+
out << "std::ostream& operator<<(std::ostream& out, const "
2067+
<< tstruct->get_name()
2068+
<< "& obj);" << '\n';
2069+
}
19852070
out << '\n';
19862071
}
19872072

19882073
void t_cpp_generator::generate_struct_ostream_operator(std::ostream& out, t_struct* tstruct) {
19892074
if (!has_custom_ostream(tstruct)) {
19902075
// thrift defines this behavior
1991-
out << "std::ostream& operator<<(std::ostream& out, const "
1992-
<< tstruct->get_name()
1993-
<< "& obj)" << '\n';
2076+
if (gen_template_streamop_) {
2077+
out << "template <typename OStream_>" << '\n';
2078+
out << "OStream_& operator<<(OStream_& out, const "
2079+
<< tstruct->get_name()
2080+
<< "& obj)" << '\n';
2081+
} else {
2082+
out << "std::ostream& operator<<(std::ostream& out, const "
2083+
<< tstruct->get_name()
2084+
<< "& obj)" << '\n';
2085+
}
19942086
scope_up(out);
19952087
out << indent() << "obj.printTo(out);" << '\n'
19962088
<< indent() << "return out;" << '\n';
@@ -2000,11 +2092,24 @@ void t_cpp_generator::generate_struct_ostream_operator(std::ostream& out, t_stru
20002092
}
20012093

20022094
void t_cpp_generator::generate_struct_print_method_decl(std::ostream& out, t_struct* tstruct) {
2003-
out << "void ";
2004-
if (tstruct) {
2005-
out << tstruct->get_name() << "::";
2095+
if (gen_template_streamop_) {
2096+
// For template version, the method itself is templated
2097+
if (!tstruct) {
2098+
// Declaration inside class - no "template" keyword here, will be added by caller if needed
2099+
out << "template <typename OStream_>" << '\n' << indent() << "void ";
2100+
} else {
2101+
// External implementation - needs template keyword
2102+
out << "template <typename OStream_>" << '\n' << indent() << "void ";
2103+
out << tstruct->get_name() << "::";
2104+
}
2105+
out << "printTo(OStream_& out) const";
2106+
} else {
2107+
out << "void ";
2108+
if (tstruct) {
2109+
out << tstruct->get_name() << "::";
2110+
}
2111+
out << "printTo(std::ostream& out) const";
20062112
}
2007-
out << "printTo(std::ostream& out) const";
20082113
}
20092114

20102115
void t_cpp_generator::generate_exception_what_method_decl(std::ostream& out,
@@ -2020,35 +2125,49 @@ void t_cpp_generator::generate_exception_what_method_decl(std::ostream& out,
20202125
}
20212126

20222127
namespace struct_ostream_operator_generator {
2023-
void generate_required_field_value(std::ostream& out, const t_field* field) {
2128+
void generate_required_field_value(std::ostream& out, const t_field* field, bool use_printto) {
2129+
if (use_printto) {
2130+
// For template_streamop, use printTo for direct streaming without temporary strings
2131+
// Use comma operator: out << "x=", printTo(out, x)
2132+
out << ", printTo(out, " << field->get_name() << ")";
2133+
return;
2134+
}
2135+
// For std::ostream, use to_string (backward compatible)
20242136
out << " << to_string(" << field->get_name() << ")";
20252137
}
20262138

2027-
void generate_optional_field_value(std::ostream& out, const t_field* field) {
2028-
out << "; (__isset." << field->get_name() << " ? (out";
2029-
generate_required_field_value(out, field);
2030-
out << ") : (out << \"<null>\"))";
2139+
void generate_optional_field_value(std::ostream& out, const t_field* field, bool use_printto) {
2140+
out << "; (__isset." << field->get_name() << " ? ";
2141+
if (use_printto) {
2142+
// For printTo, call directly without wrapping in (out ...)
2143+
out << "printTo(out, " << field->get_name() << ")";
2144+
} else {
2145+
// For to_string, need to wrap with (out << ...)
2146+
out << "(out << to_string(" << field->get_name() << "))";
2147+
}
2148+
out << " : (out << \"<null>\"))";
20312149
}
20322150

2033-
void generate_field_value(std::ostream& out, const t_field* field) {
2151+
void generate_field_value(std::ostream& out, const t_field* field, bool use_printto) {
20342152
if (field->get_req() == t_field::T_OPTIONAL)
2035-
generate_optional_field_value(out, field);
2153+
generate_optional_field_value(out, field, use_printto);
20362154
else
2037-
generate_required_field_value(out, field);
2155+
generate_required_field_value(out, field, use_printto);
20382156
}
20392157

20402158
void generate_field_name(std::ostream& out, const t_field* field) {
20412159
out << "\"" << field->get_name() << "=\"";
20422160
}
20432161

2044-
void generate_field(std::ostream& out, const t_field* field) {
2162+
void generate_field(std::ostream& out, const t_field* field, bool use_printto) {
20452163
generate_field_name(out, field);
2046-
generate_field_value(out, field);
2164+
generate_field_value(out, field, use_printto);
20472165
}
20482166

20492167
void generate_fields(std::ostream& out,
20502168
const vector<t_field*>& fields,
2051-
const std::string& indent) {
2169+
const std::string& indent,
2170+
bool use_printto) {
20522171
const vector<t_field*>::const_iterator beg = fields.begin();
20532172
const vector<t_field*>::const_iterator end = fields.end();
20542173

@@ -2059,7 +2178,7 @@ void generate_fields(std::ostream& out,
20592178
out << "\", \" << ";
20602179
}
20612180

2062-
generate_field(out, *it);
2181+
generate_field(out, *it, use_printto);
20632182
out << ";" << '\n';
20642183
}
20652184
}
@@ -2075,9 +2194,16 @@ void t_cpp_generator::generate_struct_print_method(std::ostream& out, t_struct*
20752194

20762195
indent_up();
20772196

2197+
bool use_printto = gen_template_streamop_;
2198+
if (use_printto) {
2199+
// For template_streamop, use printTo for direct streaming (better performance)
2200+
out << indent() << "using ::apache::thrift::printTo;" << '\n';
2201+
}
2202+
// Always include to_string as well for compatibility
20782203
out << indent() << "using ::apache::thrift::to_string;" << '\n';
2204+
20792205
out << indent() << "out << \"" << tstruct->get_name() << "(\";" << '\n';
2080-
struct_ostream_operator_generator::generate_fields(out, tstruct->get_members(), indent());
2206+
struct_ostream_operator_generator::generate_fields(out, tstruct->get_members(), indent(), use_printto);
20812207
out << indent() << "out << \")\";" << '\n';
20822208

20832209
indent_down();

0 commit comments

Comments
 (0)