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;
};
```
diff --git a/compiler/cpp/src/thrift/generate/t_cpp_generator.cc b/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
index 99919e2..acc40c0 100644
--- a/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
@@ -66,6 +66,7 @@
     gen_templates_only_ = false;
     gen_moveable_ = false;
     gen_forward_setter_ = false;
+    gen_template_streamop_ = false;
     gen_no_ostream_operators_ = false;
     gen_no_skeleton_ = false;
     gen_no_constructors_ = false;
@@ -96,6 +97,8 @@
         }
       } else if ( iter->first.compare("no_ostream_operators") == 0) {
         gen_no_ostream_operators_ = true;
+      } else if ( iter->first.compare("template_streamop") == 0) {
+        gen_template_streamop_ = true;
       } else if ( iter->first.compare("no_skeleton") == 0) {
         gen_no_skeleton_ = true;
       } else if ( iter->first.compare("no_constructors") == 0) {
@@ -130,6 +133,8 @@
   void generate_enum_ostream_operator(std::ostream& out, t_enum* tenum);
   void generate_enum_to_string_helper_function_decl(std::ostream& out, t_enum* tenum);
   void generate_enum_to_string_helper_function(std::ostream& out, t_enum* tenum);
+  void generate_enum_printto_helper_function_decl(std::ostream& out, t_enum* tenum);
+  void generate_enum_printto_helper_function(std::ostream& out, t_enum* tenum);
   void generate_forward_declaration(t_struct* tstruct) override;
   void generate_struct(t_struct* tstruct) override { generate_cpp_struct(tstruct, false); }
   void generate_xception(t_struct* txception) override { generate_cpp_struct(txception, true); }
@@ -372,6 +377,11 @@
   bool gen_forward_setter_;
 
   /**
+   * True if we should generate operator<< and printTo with generic stream type template.
+   */
+  bool gen_template_streamop_;
+
+  /**
    * True if we should generate ostream definitions
    */
   bool gen_no_ostream_operators_;
@@ -462,7 +472,7 @@
   string f_types_impl_name = get_out_dir() + program_name_ + "_types.cpp";
   f_types_impl_.open(f_types_impl_name.c_str());
 
-  if (gen_templates_ || gen_forward_setter_) {
+  if (gen_templates_ || gen_forward_setter_ || gen_template_streamop_) {
     // If we don't open the stream, it appears to just discard data,
     // which is fine.
     string f_types_tcc_name = get_out_dir() + program_name_ + "_types.tcc";
@@ -529,6 +539,12 @@
   f_types_impl_ << "#include <ostream>" << '\n' << '\n';
   f_types_impl_ << "#include <thrift/TToString.h>" << '\n' << '\n';
 
+  // For template_streamop, we need TPrintTo.h in the .tcc file for direct streaming
+  // TPrintTo avoids the overhead of to_string which uses ostringstream internally
+  if (gen_template_streamop_) {
+    f_types_tcc_ << "#include <thrift/TPrintTo.h>" << '\n' << '\n';
+  }
+
   // Open namespace
   ns_open_ = namespace_open(program_->get_namespace("cpp"));
   ns_close_ = namespace_close(program_->get_namespace("cpp"));
@@ -552,7 +568,7 @@
   // Include the types.tcc file from the types header file,
   // so clients don't have to explicitly include the tcc file.
   // TODO(simpkins): Make this a separate option.
-  if (gen_templates_ || gen_forward_setter_) {
+  if (gen_templates_ || gen_forward_setter_ || gen_template_streamop_) {
     f_types_ << "#include \"" << get_include_prefix(*get_program()) << program_name_
              << "_types.tcc\"" << '\n' << '\n';
   }
@@ -680,6 +696,12 @@
   generate_enum_to_string_helper_function_decl(f_types_, tenum);
   generate_enum_to_string_helper_function(f_types_impl_, tenum);
 
+  // Generate template printTo specialization for enums when template_streamop is enabled
+  if (gen_template_streamop_) {
+    generate_enum_printto_helper_function_decl(f_types_, tenum);
+    generate_enum_printto_helper_function(f_types_tcc_, tenum);
+  }
+
   has_members_ = true;
 }
 
@@ -777,6 +799,52 @@
   }
 }
 
+void t_cpp_generator::generate_enum_printto_helper_function_decl(std::ostream& out, t_enum* tenum) {
+  out << "template <typename OStream_>" << '\n';
+  out << "void printTo(OStream_& out, const ";
+  if (gen_pure_enums_) {
+    out << tenum->get_name();
+  } else {
+    out << tenum->get_name() << "::type&";
+  }
+  out << " val);" << '\n';
+  out << '\n';
+}
+
+void t_cpp_generator::generate_enum_printto_helper_function(std::ostream& out, t_enum* tenum) {
+  if (!has_custom_ostream(tenum)) {
+    out << "template <typename OStream_>" << '\n';
+    out << "void printTo(OStream_& out, const ";
+    if (gen_pure_enums_) {
+      out << tenum->get_name();
+    } else {
+      out << tenum->get_name() << "::type&";
+    }
+    out << " val) ";
+    scope_up(out);
+
+    out << indent() << "std::map<int, const char*>::const_iterator it = _"
+             << tenum->get_name() << "_VALUES_TO_NAMES.find(";
+    if (gen_enum_class_) {
+      out << "static_cast<int>(val));" << '\n';
+    } else {
+      out << "val);" << '\n';
+    }
+    out << indent() << "if (it != _" << tenum->get_name() << "_VALUES_TO_NAMES.end()) {" << '\n';
+    indent_up();
+    out << indent() << "out << it->second;" << '\n';
+    indent_down();
+    out << indent() << "} else {" << '\n';
+    indent_up();
+    out << indent() << "out << static_cast<int>(val);" << '\n';
+    indent_down();
+    out << indent() << "}" << '\n';
+
+    scope_down(out);
+    out << '\n';
+  }
+}
+
 /**
  * Generates a class that holds all the constants.
  */
@@ -1003,7 +1071,9 @@
   }
 
   if (!has_custom_ostream(tstruct)) {
-    generate_struct_print_method(f_types_impl_, tstruct);
+    // When template_streamop is enabled, printTo implementation goes to .tcc file
+    std::ostream& print_method_out = (gen_template_streamop_ ? f_types_tcc_ : f_types_impl_);
+    generate_struct_print_method(print_method_out, tstruct);
   }
 
   if (is_exception) {
@@ -1481,7 +1551,8 @@
 
   if (is_user_struct && !has_custom_ostream(tstruct)) {
     out << indent();
-    if (!gen_templates_) out << "virtual ";
+    // Template methods cannot be virtual, so skip virtual keyword when using template_streamop
+    if (!gen_templates_ && !gen_template_streamop_) out << "virtual ";
     generate_struct_print_method_decl(out, nullptr);
     out << ";" << '\n';
   }
@@ -1532,7 +1603,9 @@
   // When private_optional is enabled, optional members may be private.
   // The generated namespace-scope operator<< needs friend access.
   if (is_user_struct && gen_private_optional_) {
-    indent(out) << "friend ";
+    if (!gen_template_streamop_) {
+      indent(out) << "friend ";
+    }
     generate_struct_ostream_operator_decl(out, tstruct);
   }
 
@@ -1610,7 +1683,9 @@
     }
   }
   if (is_user_struct) {
-    generate_struct_ostream_operator(out, tstruct);
+    // When template_streamop is enabled, operator<< implementation goes to .tcc file
+    std::ostream& ostream_op_out = (gen_template_streamop_ ? f_types_tcc_ : out);
+    generate_struct_ostream_operator(ostream_op_out, tstruct);
   }
   out << '\n';
 }
@@ -1979,18 +2054,35 @@
 }
 
 void t_cpp_generator::generate_struct_ostream_operator_decl(std::ostream& out, t_struct* tstruct) {
-  out << "std::ostream& operator<<(std::ostream& out, const "
-      << tstruct->get_name()
-      << "& obj);" << '\n';
+  if (gen_template_streamop_) {
+    out << "template <typename OStream_>" << '\n';
+    if (gen_private_optional_) {
+      out << indent() << "friend ";
+    }
+    out << "OStream_& operator<<(OStream_& out, const "
+        << tstruct->get_name()
+        << "& obj);" << '\n';
+  } else {
+    out << "std::ostream& operator<<(std::ostream& out, const "
+        << tstruct->get_name()
+        << "& obj);" << '\n';
+  }
   out << '\n';
 }
 
 void t_cpp_generator::generate_struct_ostream_operator(std::ostream& out, t_struct* tstruct) {
   if (!has_custom_ostream(tstruct)) {
     // thrift defines this behavior
-    out << "std::ostream& operator<<(std::ostream& out, const "
-        << tstruct->get_name()
-        << "& obj)" << '\n';
+    if (gen_template_streamop_) {
+      out << "template <typename OStream_>" << '\n';
+      out << "OStream_& operator<<(OStream_& out, const "
+          << tstruct->get_name()
+          << "& obj)" << '\n';
+    } else {
+      out << "std::ostream& operator<<(std::ostream& out, const "
+          << tstruct->get_name()
+          << "& obj)" << '\n';
+    }
     scope_up(out);
     out << indent() << "obj.printTo(out);" << '\n'
         << indent() << "return out;" << '\n';
@@ -2000,11 +2092,24 @@
 }
 
 void t_cpp_generator::generate_struct_print_method_decl(std::ostream& out, t_struct* tstruct) {
-  out << "void ";
-  if (tstruct) {
-    out << tstruct->get_name() << "::";
+  if (gen_template_streamop_) {
+    // For template version, the method itself is templated
+    if (!tstruct) {
+      // Declaration inside class - no "template" keyword here, will be added by caller if needed
+      out << "template <typename OStream_>" << '\n' << indent() << "void ";
+    } else {
+      // External implementation - needs template keyword
+      out << "template <typename OStream_>" << '\n' << indent() << "void ";
+      out << tstruct->get_name() << "::";
+    }
+    out << "printTo(OStream_& out) const";
+  } else {
+    out << "void ";
+    if (tstruct) {
+      out << tstruct->get_name() << "::";
+    }
+    out << "printTo(std::ostream& out) const";
   }
-  out << "printTo(std::ostream& out) const";
 }
 
 void t_cpp_generator::generate_exception_what_method_decl(std::ostream& out,
@@ -2020,35 +2125,49 @@
 }
 
 namespace struct_ostream_operator_generator {
-void generate_required_field_value(std::ostream& out, const t_field* field) {
+void generate_required_field_value(std::ostream& out, const t_field* field, bool use_printto) {
+  if (use_printto) {
+    // For template_streamop, use printTo for direct streaming without temporary strings
+    // Use comma operator: out << "x=", printTo(out, x)
+    out << ", printTo(out, " << field->get_name() << ")";
+    return;
+  }
+  // For std::ostream, use to_string (backward compatible)
   out << " << to_string(" << field->get_name() << ")";
 }
 
-void generate_optional_field_value(std::ostream& out, const t_field* field) {
-  out << "; (__isset." << field->get_name() << " ? (out";
-  generate_required_field_value(out, field);
-  out << ") : (out << \"<null>\"))";
+void generate_optional_field_value(std::ostream& out, const t_field* field, bool use_printto) {
+  out << "; (__isset." << field->get_name() << " ? ";
+  if (use_printto) {
+    // For printTo, call directly without wrapping in (out ...)
+    out << "printTo(out, " << field->get_name() << ")";
+  } else {
+    // For to_string, need to wrap with (out << ...)
+    out << "(out << to_string(" << field->get_name() << "))";
+  }
+  out << " : (out << \"<null>\"))";
 }
 
-void generate_field_value(std::ostream& out, const t_field* field) {
+void generate_field_value(std::ostream& out, const t_field* field, bool use_printto) {
   if (field->get_req() == t_field::T_OPTIONAL)
-    generate_optional_field_value(out, field);
+    generate_optional_field_value(out, field, use_printto);
   else
-    generate_required_field_value(out, field);
+    generate_required_field_value(out, field, use_printto);
 }
 
 void generate_field_name(std::ostream& out, const t_field* field) {
   out << "\"" << field->get_name() << "=\"";
 }
 
-void generate_field(std::ostream& out, const t_field* field) {
+void generate_field(std::ostream& out, const t_field* field, bool use_printto) {
   generate_field_name(out, field);
-  generate_field_value(out, field);
+  generate_field_value(out, field, use_printto);
 }
 
 void generate_fields(std::ostream& out,
                      const vector<t_field*>& fields,
-                     const std::string& indent) {
+                     const std::string& indent,
+                     bool use_printto) {
   const vector<t_field*>::const_iterator beg = fields.begin();
   const vector<t_field*>::const_iterator end = fields.end();
 
@@ -2059,7 +2178,7 @@
       out << "\", \" << ";
     }
 
-    generate_field(out, *it);
+    generate_field(out, *it, use_printto);
     out << ";" << '\n';
   }
 }
@@ -2075,9 +2194,16 @@
 
   indent_up();
 
+  bool use_printto = gen_template_streamop_;
+  if (use_printto) {
+    // For template_streamop, use printTo for direct streaming (better performance)
+    out << indent() << "using ::apache::thrift::printTo;" << '\n';
+  }
+  // Always include to_string as well for compatibility
   out << indent() << "using ::apache::thrift::to_string;" << '\n';
+  
   out << indent() << "out << \"" << tstruct->get_name() << "(\";" << '\n';
-  struct_ostream_operator_generator::generate_fields(out, tstruct->get_members(), indent());
+  struct_ostream_operator_generator::generate_fields(out, tstruct->get_members(), indent(), use_printto);
   out << indent() << "out << \")\";" << '\n';
 
   indent_down();
diff --git a/compiler/cpp/tests/cpp/t_cpp_generator_template_streamop_tests.cc b/compiler/cpp/tests/cpp/t_cpp_generator_template_streamop_tests.cc
new file mode 100644
index 0000000..bb17b23
--- /dev/null
+++ b/compiler/cpp/tests/cpp/t_cpp_generator_template_streamop_tests.cc
@@ -0,0 +1,166 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+// 
+//     http://www.apache.org/licenses/LICENSE-2.0
+// 
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "t_cpp_generator_test_utils.h"
+
+using std::string;
+using std::map;
+using cpp_generator_test_utils::read_file;
+using cpp_generator_test_utils::source_dir;
+using cpp_generator_test_utils::join_path;
+using cpp_generator_test_utils::normalize_for_compare;
+using cpp_generator_test_utils::parse_thrift_for_test;
+
+// Helper function declared in t_cpp_generator_private_optional_tests.cc
+// Extracts the class definition from generated content for the given class name
+extern string extract_class_definition(const string& content, const string& class_name);
+
+TEST_CASE("t_cpp_generator without template_streamop generates standard operator<< and printTo", "[functional]")
+{
+    string path = join_path(source_dir(), "test_template_streamop.thrift");
+    string name = "test_template_streamop";
+    map<string, string> parsed_options = {};
+    string option_string = "";
+
+    std::unique_ptr<t_program> program(new t_program(path, name));
+    parse_thrift_for_test(program.get());
+    
+    std::unique_ptr<t_generator> gen(
+        t_generator_registry::get_generator(program.get(), "cpp", parsed_options, option_string));
+    REQUIRE(gen != nullptr);
+    
+    // Generate code
+    REQUIRE_NOTHROW(gen->generate_program());
+
+    // Read generated output
+    string generated_file = "gen-cpp/test_template_streamop_types.h";
+    string generated_content = read_file(generated_file);
+    REQUIRE(!generated_content.empty());
+
+    // Verify operator<< declaration uses std::ostream (not template)
+    REQUIRE(generated_content.find("std::ostream& operator<<(std::ostream& out, const SimpleStruct& obj);") != string::npos);
+    
+    // Extract class definition to check printTo
+    string class_def = extract_class_definition(generated_content, "SimpleStruct");
+    REQUIRE(!class_def.empty());
+    
+    // Verify printTo method uses std::ostream (not template)
+    REQUIRE(class_def.find("void printTo(std::ostream& out) const;") != string::npos);
+    
+    // Verify no template syntax for printTo or operator<<
+    REQUIRE(class_def.find("template <typename OStream_>") == string::npos);
+    
+    // Read implementation file
+    string impl_file = "gen-cpp/test_template_streamop_types.cpp";
+    string impl_content = read_file(impl_file);
+    REQUIRE(!impl_content.empty());
+    
+    // Verify implementation also uses std::ostream
+    REQUIRE(impl_content.find("void SimpleStruct::printTo(std::ostream& out) const") != string::npos);
+    REQUIRE(impl_content.find("std::ostream& operator<<(std::ostream& out, const SimpleStruct& obj)") != string::npos);
+}
+
+TEST_CASE("t_cpp_generator with template_streamop generates templated operator<< and printTo", "[functional]")
+{
+    string path = join_path(source_dir(), "test_template_streamop.thrift");
+    string name = "test_template_streamop";
+    map<string, string> parsed_options = {{"template_streamop", ""}};
+    string option_string = "";
+
+    std::unique_ptr<t_program> program(new t_program(path, name));
+    parse_thrift_for_test(program.get());
+    
+    std::unique_ptr<t_generator> gen(
+        t_generator_registry::get_generator(program.get(), "cpp", parsed_options, option_string));
+    REQUIRE(gen != nullptr);
+    
+    // Generate code
+    REQUIRE_NOTHROW(gen->generate_program());
+
+    // Read generated header
+    string generated_file = "gen-cpp/test_template_streamop_types.h";
+    string generated_content = read_file(generated_file);
+    REQUIRE(!generated_content.empty());
+
+    // Verify operator<< declaration uses template syntax
+    REQUIRE(generated_content.find("template <typename OStream_>") != string::npos);
+    REQUIRE(generated_content.find("OStream_& operator<<(OStream_& out, const SimpleStruct& obj);") != string::npos);
+    
+    // Extract class definition
+    string class_def = extract_class_definition(generated_content, "SimpleStruct");
+    REQUIRE(!class_def.empty());
+    
+    // Verify printTo method declaration uses template syntax
+    REQUIRE(class_def.find("template <typename OStream_>") != string::npos);
+    REQUIRE(class_def.find("void printTo(OStream_& out) const;") != string::npos);
+    
+    // Verify no hardcoded std::ostream in printTo declaration
+    REQUIRE(class_def.find("void printTo(std::ostream& out) const;") == string::npos);
+    
+    // Read implementation file (.tcc for templates)
+    string impl_file = "gen-cpp/test_template_streamop_types.tcc";
+    string impl_content = read_file(impl_file);
+    REQUIRE(!impl_content.empty());
+    
+    // Verify implementation uses template syntax
+    REQUIRE(impl_content.find("template <typename OStream_>") != string::npos);
+    REQUIRE(impl_content.find("void SimpleStruct::printTo(OStream_& out) const") != string::npos);
+    REQUIRE(impl_content.find("OStream_& operator<<(OStream_& out, const SimpleStruct& obj)") != string::npos);
+    
+    // Verify both SimpleStruct and NestedStruct have template versions
+    REQUIRE(impl_content.find("void NestedStruct::printTo(OStream_& out) const") != string::npos);
+}
+
+TEST_CASE("t_cpp_generator with template_streamop and private_optional generates correct friend declarations", "[functional]")
+{
+    string path = join_path(source_dir(), "test_template_streamop.thrift");
+    string name = "test_template_streamop";
+    map<string, string> parsed_options = {{"template_streamop", ""}, {"private_optional", ""}};
+    string option_string = "";
+
+    std::unique_ptr<t_program> program(new t_program(path, name));
+    parse_thrift_for_test(program.get());
+    
+    std::unique_ptr<t_generator> gen(
+        t_generator_registry::get_generator(program.get(), "cpp", parsed_options, option_string));
+    REQUIRE(gen != nullptr);
+    
+    // Generate code
+    REQUIRE_NOTHROW(gen->generate_program());
+
+    // Read generated header
+    string generated_file = "gen-cpp/test_template_streamop_types.h";
+    string generated_content = read_file(generated_file);
+    REQUIRE(!generated_content.empty());
+
+    // Extract class definition
+    string class_def = extract_class_definition(generated_content, "SimpleStruct");
+    REQUIRE(!class_def.empty());
+    
+    // Verify friend declaration comes after template keyword (correct C++ syntax)
+    // Should be: template <typename OStream_>
+    //            friend OStream_& operator<<(...)
+    // NOT: friend template <typename OStream_> ...
+    REQUIRE(class_def.find("template <typename OStream_>") != string::npos);
+    REQUIRE(class_def.find("friend OStream_& operator<<(OStream_& out, const SimpleStruct& obj);") != string::npos);
+    
+    // Verify incorrect syntax is not present
+    REQUIRE(class_def.find("friend template <typename OStream_>") == string::npos);
+}
+
+// Note: Enum printTo specialization functionality is tested in the runtime test (TemplateStreamOpTest)
+// since the compiler test infrastructure doesn't support this testing pattern.
diff --git a/compiler/cpp/tests/cpp/test_template_streamop.thrift b/compiler/cpp/tests/cpp/test_template_streamop.thrift
new file mode 100644
index 0000000..1d6c0c8
--- /dev/null
+++ b/compiler/cpp/tests/cpp/test_template_streamop.thrift
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+// 
+//     http://www.apache.org/licenses/LICENSE-2.0
+// 
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace cpp test.template_streamop
+
+enum Status {
+  ACTIVE = 1,
+  INACTIVE = 2,
+  PENDING = 3,
+}
+
+struct SimpleStruct {
+  1: i32 id;
+  2: string name;
+  3: optional string description;
+}
+
+struct NestedStruct {
+  1: i32 value;
+  2: SimpleStruct inner;
+}
+
+struct StructWithEnum {
+  1: Status status;
+  2: string name;
+}
diff --git a/lib/cpp/Makefile.am b/lib/cpp/Makefile.am
index 3d7beab..dcbcf2c 100644
--- a/lib/cpp/Makefile.am
+++ b/lib/cpp/Makefile.am
@@ -143,6 +143,7 @@
                          src/thrift/TProcessor.h \
                          src/thrift/TApplicationException.h \
                          src/thrift/TLogging.h \
+                         src/thrift/TPrintTo.h \
                          src/thrift/TToString.h \
                          src/thrift/TBase.h \
                          src/thrift/TConfiguration.h \
diff --git a/lib/cpp/src/thrift/TPrintTo.h b/lib/cpp/src/thrift/TPrintTo.h
new file mode 100644
index 0000000..d3d5cb4
--- /dev/null
+++ b/lib/cpp/src/thrift/TPrintTo.h
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef _THRIFT_TPRINTTO_H_
+#define _THRIFT_TPRINTTO_H_ 1
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace apache {
+namespace thrift {
+
+// Generic printTo template - streams value directly to output
+template <typename OStream, typename T>
+void printTo(OStream& out, const T& t) {
+  out << t;
+}
+
+// Special handling of i8 datatypes (THRIFT-5272) - cast to int to avoid char output
+template <typename OStream>
+void printTo(OStream& out, const int8_t& t) {
+  out << static_cast<int>(t);
+}
+
+// Forward declarations for collection types
+template <typename OStream, typename K, typename V>
+void printTo(OStream& out, const std::map<K, V>& m);
+
+template <typename OStream, typename T>
+void printTo(OStream& out, const std::set<T>& s);
+
+template <typename OStream, typename T>
+void printTo(OStream& out, const std::vector<T>& t);
+
+// Pair support
+template <typename OStream, typename K, typename V>
+void printTo(OStream& out, const std::pair<K, V>& v) {
+  printTo(out, v.first);
+  out << ": ";
+  printTo(out, v.second);
+}
+
+// Iterator range support
+template <typename OStream, typename Iterator>
+void printTo(OStream& out, Iterator beg, Iterator end) {
+  for (Iterator it = beg; it != end; ++it) {
+    if (it != beg)
+      out << ", ";
+    printTo(out, *it);
+  }
+}
+
+// Vector support
+template <typename OStream, typename T>
+void printTo(OStream& out, const std::vector<T>& t) {
+  out << "[";
+  printTo(out, t.begin(), t.end());
+  out << "]";
+}
+
+// Map support
+template <typename OStream, typename K, typename V>
+void printTo(OStream& out, const std::map<K, V>& m) {
+  out << "{";
+  printTo(out, m.begin(), m.end());
+  out << "}";
+}
+
+// Set support
+template <typename OStream, typename T>
+void printTo(OStream& out, const std::set<T>& s) {
+  out << "{";
+  printTo(out, s.begin(), s.end());
+  out << "}";
+}
+
+} // namespace thrift
+} // namespace apache
+
+#endif // _THRIFT_TPRINTTO_H_
diff --git a/test/cpp/CMakeLists.txt b/test/cpp/CMakeLists.txt
index 2403c87..6fdc95e 100644
--- a/test/cpp/CMakeLists.txt
+++ b/test/cpp/CMakeLists.txt
@@ -164,6 +164,29 @@
 target_link_libraries(EnumClassTest thrift)
 add_test(NAME EnumClassTest COMMAND EnumClassTest)
 
+# TemplateStreamOpTest - tests the template_streamop option
+set(templatestreamoptestgencpp_SOURCES
+    gen-cpp-templatestreamop/gen-cpp/ThriftTest_types.cpp
+    gen-cpp-templatestreamop/gen-cpp/ThriftTest_constants.cpp
+    src/ThriftTest_extras.cpp
+)
+add_library(templatestreamoptestgencpp STATIC ${templatestreamoptestgencpp_SOURCES})
+target_include_directories(templatestreamoptestgencpp BEFORE PRIVATE 
+    "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-templatestreamop"
+    "${CMAKE_CURRENT_BINARY_DIR}"
+    "${PROJECT_SOURCE_DIR}/lib/cpp/src"
+)
+target_link_libraries(templatestreamoptestgencpp thrift)
+
+add_executable(TemplateStreamOpTest src/TemplateStreamOpTest.cpp)
+target_include_directories(TemplateStreamOpTest BEFORE PRIVATE 
+    "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-templatestreamop/gen-cpp"
+    "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-templatestreamop"
+)
+target_link_libraries(TemplateStreamOpTest templatestreamoptestgencpp ${Boost_LIBRARIES})
+target_link_libraries(TemplateStreamOpTest thrift)
+add_test(NAME TemplateStreamOpTest COMMAND TemplateStreamOpTest)
+
 #
 # Common thrift code generation rules
 #
@@ -193,6 +216,13 @@
     WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
 )
 
+# Generate ThriftTest with template_streamop option for TemplateStreamOpTest
+add_custom_command(OUTPUT gen-cpp-templatestreamop/gen-cpp/ThriftTest_types.cpp gen-cpp-templatestreamop/gen-cpp/ThriftTest_types.h gen-cpp-templatestreamop/gen-cpp/ThriftTest_constants.cpp
+    COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-templatestreamop
+    COMMAND ${THRIFT_COMPILER} --gen cpp:template_streamop -o gen-cpp-templatestreamop ${PROJECT_SOURCE_DIR}/test/ThriftTest.thrift
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
 add_custom_command(OUTPUT gen-cpp/Service.cpp
     COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/StressTest.thrift
 )
diff --git a/test/cpp/Makefile.am b/test/cpp/Makefile.am
index f2d07ec..720a4b3 100644
--- a/test/cpp/Makefile.am
+++ b/test/cpp/Makefile.am
@@ -28,7 +28,10 @@
                 gen-cpp-private/ThriftTest_types.cpp \
                 gen-cpp-private/ThriftTest_constants.cpp \
                 gen-cpp-enumclass/ThriftTest_types.cpp \
-                gen-cpp-enumclass/ThriftTest_constants.cpp
+                gen-cpp-enumclass/ThriftTest_constants.cpp \
+                gen-cpp-templatestreamop/ThriftTest_types.cpp \
+                gen-cpp-templatestreamop/ThriftTest_types.tcc \
+                gen-cpp-templatestreamop/ThriftTest_constants.cpp
 
 noinst_LTLIBRARIES = libtestgencpp.la libstresstestgencpp.la
 nodist_libtestgencpp_la_SOURCES = \
@@ -51,7 +54,8 @@
 noinst_LTLIBRARIES += \
 	libforwardsettertestgencpp.la \
 	libprivateoptonaltestgencpp.la \
-	libenumclasstestgencpp.la
+	libenumclasstestgencpp.la \
+	libtemplatestreamoptestgencpp.la
 
 nodist_libforwardsettertestgencpp_la_SOURCES = \
 	gen-cpp-forward/ThriftTest_types.cpp \
@@ -81,6 +85,16 @@
 
 libenumclasstestgencpp_la_LIBADD = $(top_builddir)/lib/cpp/libthrift.la
 
+nodist_libtemplatestreamoptestgencpp_la_SOURCES = \
+	gen-cpp-templatestreamop/ThriftTest_types.cpp \
+	gen-cpp-templatestreamop/ThriftTest_types.h \
+	gen-cpp-templatestreamop/ThriftTest_types.tcc \
+	gen-cpp-templatestreamop/ThriftTest_constants.cpp \
+	gen-cpp-templatestreamop/ThriftTest_constants.h \
+	src/ThriftTest_extras.cpp
+
+libtemplatestreamoptestgencpp_la_LIBADD = $(top_builddir)/lib/cpp/libthrift.la
+
 nodist_libstresstestgencpp_la_SOURCES = \
 	gen-cpp/StressTest_types.h \
 	gen-cpp/Service.cpp \
@@ -97,7 +111,8 @@
 	StressTestNonBlocking \
 	ForwardSetterTest \
 	PrivateOptionalTest \
-	EnumClassTest
+	EnumClassTest \
+	TemplateStreamOpTest
 
 # we currently do not run the testsuite, stop c++ server issue
 # TESTS = \
@@ -162,6 +177,14 @@
 	libenumclasstestgencpp.la \
 	$(top_builddir)/lib/cpp/libthrift.la
 
+TemplateStreamOpTest_SOURCES = \
+	src/TemplateStreamOpTest.cpp
+
+TemplateStreamOpTest_CPPFLAGS = -Igen-cpp-templatestreamop $(AM_CPPFLAGS)
+TemplateStreamOpTest_LDADD = \
+	libtemplatestreamoptestgencpp.la \
+	$(top_builddir)/lib/cpp/libthrift.la
+
 #
 # Common thrift code generation rules
 #
@@ -183,6 +206,11 @@
 	$(MKDIR_P) gen-cpp-enumclass
 	$(THRIFT) --gen cpp:pure_enums=enum_class -out gen-cpp-enumclass $<
 
+# Generate ThriftTest with template_streamop option
+gen-cpp-templatestreamop/ThriftTest_types.cpp gen-cpp-templatestreamop/ThriftTest_types.h gen-cpp-templatestreamop/ThriftTest_types.tcc gen-cpp-templatestreamop/ThriftTest_constants.cpp: $(top_srcdir)/test/ThriftTest.thrift $(THRIFT)
+	$(MKDIR_P) gen-cpp-templatestreamop
+	$(THRIFT) --gen cpp:template_streamop -out gen-cpp-templatestreamop $<
+
 gen-cpp/Service.cpp: $(top_srcdir)/test/StressTest.thrift $(THRIFT)
 	$(THRIFT) --gen cpp $<
 
@@ -194,7 +222,7 @@
 AM_LDFLAGS = $(BOOST_LDFLAGS) $(LIBEVENT_LDFLAGS) $(ZLIB_LIBS)
 
 clean-local:
-	$(RM) -r gen-cpp/ gen-cpp-forward/ gen-cpp-private/ gen-cpp-enumclass/
+	$(RM) -r gen-cpp/ gen-cpp-forward/ gen-cpp-private/ gen-cpp-enumclass/ gen-cpp-templatestreamop/
 
 style-local:
 	$(CPPSTYLE_CMD)
@@ -209,4 +237,5 @@
 	src/StressTestNonBlocking.cpp \
 	src/ForwardSetterTest.cpp \
 	src/PrivateOptionalTest.cpp \
-	src/EnumClassTest.cpp
+	src/EnumClassTest.cpp \
+	src/TemplateStreamOpTest.cpp
diff --git a/test/cpp/src/EnumClassTest.cpp b/test/cpp/src/EnumClassTest.cpp
index 3b21063..673d6fb 100644
--- a/test/cpp/src/EnumClassTest.cpp
+++ b/test/cpp/src/EnumClassTest.cpp
@@ -23,8 +23,10 @@
  */
 
 #include <iostream>
+#include <sstream>
 #include <cassert>
 #include <type_traits>
+#include <string>
 
 // Include generated thrift types with enum_class option
 #include "ThriftTest_types.h"
@@ -86,7 +88,88 @@
         std::cout << "  ✓ Enum class in switch statements works" << std::endl;
     }
     
+    // Test 5: Verify to_string() works with enum class
+    {
+        Numberz one = Numberz::ONE;
+        Numberz five = Numberz::FIVE;
+        Numberz eight = Numberz::EIGHT;
+        
+        std::string str_one = to_string(one);
+        std::string str_five = to_string(five);
+        std::string str_eight = to_string(eight);
+        
+        assert(str_one == "ONE");
+        assert(str_five == "FIVE");
+        assert(str_eight == "EIGHT");
+        std::cout << "  ✓ to_string() with enum class works (ONE, FIVE, EIGHT)" << std::endl;
+    }
+    
+    // Test 6: Verify operator<< works with enum class
+    {
+        Numberz two = Numberz::TWO;
+        Numberz three = Numberz::THREE;
+        
+        std::ostringstream oss;
+        oss << two << " and " << three;
+        
+        std::string result = oss.str();
+        assert(result == "TWO and THREE");
+        std::cout << "  ✓ operator<< with enum class works (TWO and THREE)" << std::endl;
+    }
+    
+    // Test 7: Verify to_string() for invalid/cast enum values
+    {
+        // Cast an invalid value to enum (edge case testing)
+        Numberz invalid = static_cast<Numberz>(999);
+        std::string str_invalid = to_string(invalid);
+        
+        // Should fall back to numeric representation
+        assert(str_invalid == "999");
+        std::cout << "  ✓ to_string() handles invalid enum values (999)" << std::endl;
+    }
+    
+    // Test 8: Verify operator<< for invalid/cast enum values
+    {
+        Numberz invalid = static_cast<Numberz>(777);
+        std::ostringstream oss;
+        oss << invalid;
+        
+        std::string result = oss.str();
+        assert(result == "777");
+        std::cout << "  ✓ operator<< handles invalid enum values (777)" << std::endl;
+    }
+    
+    // Test 9: Verify enum class with zero value
+    {
+        Numberz zero = static_cast<Numberz>(0);
+        std::string str_zero = to_string(zero);
+        
+        std::ostringstream oss;
+        oss << zero;
+        
+        // Both should output "0" since there's no named value
+        assert(str_zero == "0");
+        assert(oss.str() == "0");
+        std::cout << "  ✓ to_string() and operator<< work with zero value" << std::endl;
+    }
+    
+    // Test 10: Verify all Numberz enum values can be converted to string
+    {
+        std::ostringstream oss;
+        oss << Numberz::ONE << ", "
+            << Numberz::TWO << ", "
+            << Numberz::THREE << ", "
+            << Numberz::FIVE << ", "
+            << Numberz::SIX << ", "
+            << Numberz::EIGHT;
+        
+        std::string result = oss.str();
+        assert(result == "ONE, TWO, THREE, FIVE, SIX, EIGHT");
+        std::cout << "  ✓ All Numberz enum values stream correctly" << std::endl;
+    }
+    
     std::cout << "\n✅ All pure_enums=enum_class tests passed!" << std::endl;
     std::cout << "   Verified at compile-time: enum class properties enforced" << std::endl;
+    std::cout << "   Verified at runtime: to_string(), operator<< work correctly" << std::endl;
     return 0;
 }
diff --git a/test/cpp/src/TemplateStreamOpTest.cpp b/test/cpp/src/TemplateStreamOpTest.cpp
new file mode 100644
index 0000000..d97288d
--- /dev/null
+++ b/test/cpp/src/TemplateStreamOpTest.cpp
@@ -0,0 +1,472 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Test file to verify that template_streamop generated code compiles and works correctly.
+ * This tests the templated operator<< and printTo with various stream types.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <map>
+#include <cassert>
+#include <chrono>
+#include <cstring>
+#include <cstdio>
+#include <cstdint>
+
+// Include generated thrift types with template_streamop option
+#include "ThriftTest_types.h"
+#include <thrift/TToString.h>
+
+using namespace thrift::test;
+
+// Custom minimal stream implementation for testing and performance comparison
+class MinimalStream {
+private:
+    static constexpr size_t STACK_BUFFER_SIZE = 2048;
+    char stack_buffer_[STACK_BUFFER_SIZE];
+    char* buffer_;
+    size_t size_;
+    size_t capacity_;
+    bool on_heap_;
+    
+    void ensure_capacity(size_t additional) {
+        size_t needed = size_ + additional;
+        if (needed <= capacity_) return;
+        
+        size_t new_capacity = capacity_;
+        while (new_capacity < needed) {
+            new_capacity *= 2;
+        }
+        
+        char* new_buffer = new char[new_capacity];
+        if (size_ > 0) {
+            std::memcpy(new_buffer, buffer_, size_);
+        }
+        
+        if (on_heap_) {
+            delete[] buffer_;
+        }
+        
+        buffer_ = new_buffer;
+        capacity_ = new_capacity;
+        on_heap_ = true;
+    }
+    
+    void append(const char* s, size_t len) {
+        ensure_capacity(len);
+        std::memcpy(buffer_ + size_, s, len);
+        size_ += len;
+    }
+    
+    // Helper to print integer directly to buffer
+    template<typename T>
+    void print_integer(T value) {
+        char temp[32];  // Enough for any 64-bit integer
+        char* p = temp + sizeof(temp);
+        bool negative = value < 0;
+        
+        if (negative) {
+            value = -value;
+        }
+        
+        do {
+            *--p = '0' + (value % 10);
+            value /= 10;
+        } while (value > 0);
+        
+        if (negative) {
+            *--p = '-';
+        }
+        
+        append(p, temp + sizeof(temp) - p);
+    }
+    
+    // Helper to print unsigned integer directly to buffer
+    template<typename T>
+    void print_unsigned(T value) {
+        char temp[32];
+        char* p = temp + sizeof(temp);
+        
+        do {
+            *--p = '0' + (value % 10);
+            value /= 10;
+        } while (value > 0);
+        
+        append(p, temp + sizeof(temp) - p);
+    }
+    
+public:
+    MinimalStream() 
+        : buffer_(stack_buffer_), size_(0), capacity_(STACK_BUFFER_SIZE), on_heap_(false) {}
+    
+    ~MinimalStream() {
+        if (on_heap_) {
+            delete[] buffer_;
+        }
+    }
+    
+    MinimalStream& operator<<(const std::string& s) {
+        append(s.c_str(), s.size());
+        return *this;
+    }
+    
+    MinimalStream& operator<<(const char* s) {
+        append(s, std::strlen(s));
+        return *this;
+    }
+    
+    MinimalStream& operator<<(char c) {
+        ensure_capacity(1);
+        buffer_[size_++] = c;
+        return *this;
+    }
+    
+    MinimalStream& operator<<(int32_t i) {
+        print_integer(i);
+        return *this;
+    }
+    
+    MinimalStream& operator<<(int64_t i) {
+        print_integer(i);
+        return *this;
+    }
+    
+    MinimalStream& operator<<(uint32_t i) {
+        print_unsigned(i);
+        return *this;
+    }
+    
+    MinimalStream& operator<<(uint64_t i) {
+        print_unsigned(i);
+        return *this;
+    }
+    
+    MinimalStream& operator<<(double d) {
+        // For doubles, we still need sprintf for proper formatting
+        char temp[64];
+        int len = std::snprintf(temp, sizeof(temp), "%g", d);
+        if (len > 0) {
+            append(temp, len);
+        }
+        return *this;
+    }
+    
+    MinimalStream& operator<<(bool b) {
+        if (b) {
+            append("true", 4);
+        } else {
+            append("false", 5);
+        }
+        return *this;
+    }
+    
+    std::string str() const {
+        return std::string(buffer_, size_);
+    }
+    
+    void clear() {
+        if (on_heap_) {
+            delete[] buffer_;
+            buffer_ = stack_buffer_;
+            capacity_ = STACK_BUFFER_SIZE;
+            on_heap_ = false;
+        }
+        size_ = 0;
+    }
+};
+
+int main() {
+    std::cout << "Testing template_streamop with ThriftTest types..." << std::endl;
+    
+    // Test 1: Test with std::ostringstream
+    {
+        Xtruct x;
+        x.__set_string_thing("test string");
+        x.__set_byte_thing(42);
+        x.__set_i32_thing(12345);
+        x.__set_i64_thing(9876543210LL);
+        
+        std::ostringstream oss;
+        oss << x;
+        std::string result = oss.str();
+        
+        std::cout << "  Generated output: " << result << std::endl;
+        
+        assert(!result.empty());
+        assert(result.find("test string") != std::string::npos);
+        assert(result.find("42") != std::string::npos);
+        assert(result.find("12345") != std::string::npos);
+        std::cout << "  ✓ std::ostringstream works: " << result << std::endl;
+    }
+    
+    // Test 2: Test with custom MinimalStream
+    {
+        Xtruct x;
+        x.__set_string_thing("custom stream");
+        x.__set_byte_thing(7);
+        x.__set_i32_thing(999);
+        x.__set_i64_thing(1234567890LL);
+        
+        MinimalStream ms;
+        ms << x;
+        std::string result = ms.str();
+        
+        assert(!result.empty());
+        assert(result.find("custom stream") != std::string::npos);
+        assert(result.find("7") != std::string::npos);
+        assert(result.find("999") != std::string::npos);
+        std::cout << "  ✓ MinimalStream works: " << result << std::endl;
+    }
+    
+    // Test 3: Test nested structures
+    {
+        Xtruct x;
+        x.__set_string_thing("inner");
+        x.__set_i32_thing(100);
+        
+        Xtruct2 x2;
+        x2.__set_byte_thing(5);
+        x2.__set_struct_thing(x);
+        x2.__set_i32_thing(200);
+        
+        std::ostringstream oss;
+        oss << x2;
+        std::string result = oss.str();
+        
+        assert(!result.empty());
+        assert(result.find("inner") != std::string::npos);
+        assert(result.find("100") != std::string::npos);
+        assert(result.find("200") != std::string::npos);
+        std::cout << "  ✓ Nested structures work" << std::endl;
+    }
+    
+    // Test 4: Test optional fields
+    {
+        Bonk bonk;
+        bonk.__set_message("test message");
+        bonk.__set_type(42);
+        
+        std::ostringstream oss;
+        oss << bonk;
+        std::string result = oss.str();
+        
+        assert(!result.empty());
+        assert(result.find("test message") != std::string::npos);
+        assert(result.find("42") != std::string::npos);
+        std::cout << "  ✓ Optional fields work" << std::endl;
+    }
+    
+    // Test 5: Test structs with map/set/list/vector
+    {
+        std::cout << "\n  Testing collection types..." << std::endl;
+        
+        // Create an Insanity struct with map and list
+        Insanity insanity;
+        
+        // Add items to the map
+        std::map<Numberz::type, UserId> userMap;
+        userMap[Numberz::ONE] = 1;
+        userMap[Numberz::FIVE] = 5;
+        insanity.__set_userMap(userMap);
+        
+        // Add items to the list
+        std::vector<Xtruct> xtructs;
+        Xtruct x1;
+        x1.__set_string_thing("first");
+        x1.__set_i32_thing(111);
+        xtructs.push_back(x1);
+        
+        Xtruct x2;
+        x2.__set_string_thing("second");
+        x2.__set_i32_thing(222);
+        xtructs.push_back(x2);
+        insanity.__set_xtructs(xtructs);
+        
+        // Test with std::ostringstream
+        std::ostringstream oss;
+        oss << insanity;
+        std::string result = oss.str();
+        
+        std::cout << "    std::ostringstream output: " << result << std::endl;
+        assert(!result.empty());
+        assert(result.find("Insanity") != std::string::npos);
+        assert(result.find("userMap") != std::string::npos);
+        assert(result.find("xtructs") != std::string::npos);
+        
+        // Test with MinimalStream
+        MinimalStream ms;
+        ms << insanity;
+        std::string ms_result = ms.str();
+        
+        std::cout << "    MinimalStream output: " << ms_result << std::endl;
+        assert(!ms_result.empty());
+        assert(ms_result.find("Insanity") != std::string::npos);
+        
+        std::cout << "  ✓ Map/List collections work with both streams" << std::endl;
+    }
+    
+    // Test 6: Test to_string compatibility with collection structs
+    {
+        std::cout << "\n  Testing to_string with collection structs..." << std::endl;
+        
+        Insanity insanity;
+        std::map<Numberz::type, UserId> userMap;
+        userMap[Numberz::TWO] = 2;
+        insanity.__set_userMap(userMap);
+        
+        std::vector<Xtruct> xtructs;
+        Xtruct x;
+        x.__set_string_thing("test");
+        x.__set_i32_thing(42);
+        xtructs.push_back(x);
+        insanity.__set_xtructs(xtructs);
+        
+        // to_string should work with the generated types
+        std::string str_result = apache::thrift::to_string(insanity);
+        
+        std::cout << "    to_string output: " << str_result << std::endl;
+        assert(!str_result.empty());
+        assert(str_result.find("Insanity") != std::string::npos);
+        
+        std::cout << "  ✓ to_string works with collection structs" << std::endl;
+    }
+    
+    // Test 7: Test enum output - should print by name
+    {
+        std::cout << "\n  Testing enum output..." << std::endl;
+        
+        // Create a struct with an enum field
+        Insanity insanity;
+        std::map<Numberz::type, UserId> userMap;
+        userMap[Numberz::ONE] = 1;
+        userMap[Numberz::FIVE] = 5;
+        userMap[Numberz::TWO] = 2;
+        insanity.__set_userMap(userMap);
+        
+        // Test with std::ostringstream
+        std::ostringstream oss;
+        oss << insanity;
+        std::string result = oss.str();
+        
+        std::cout << "    std::ostringstream output: " << result << std::endl;
+        assert(result.find("ONE") != std::string::npos || result.find("1") != std::string::npos);
+        
+        // Test with MinimalStream
+        MinimalStream ms;
+        ms << insanity;
+        std::string ms_result = ms.str();
+        
+        std::cout << "    MinimalStream output: " << ms_result << std::endl;
+        assert(!ms_result.empty());
+        
+        std::cout << "  ✓ Enum fields output correctly" << std::endl;
+    }
+    
+    // Test 8: Test floating point types
+    {
+        std::cout << "\n  Testing floating point types..." << std::endl;
+        
+        // Note: ThriftTest doesn't have a struct with float/double fields
+        // So we test directly with printTo
+        float f = 3.14159f;
+        double d = 2.71828;
+        
+        // Test with std::ostringstream
+        std::ostringstream oss_f, oss_d;
+        apache::thrift::printTo(oss_f, f);
+        apache::thrift::printTo(oss_d, d);
+        
+        std::string f_result = oss_f.str();
+        std::string d_result = oss_d.str();
+        
+        std::cout << "    float printTo: " << f_result << std::endl;
+        std::cout << "    double printTo: " << d_result << std::endl;
+        
+        assert(!f_result.empty());
+        assert(!d_result.empty());
+        assert(f_result.find("3.14") != std::string::npos || f_result.find("3,14") != std::string::npos);
+        assert(d_result.find("2.71") != std::string::npos || d_result.find("2,71") != std::string::npos);
+        
+        // Test with MinimalStream
+        MinimalStream ms_f, ms_d;
+        apache::thrift::printTo(ms_f, f);
+        apache::thrift::printTo(ms_d, d);
+        
+        std::cout << "    MinimalStream float: " << ms_f.str() << std::endl;
+        std::cout << "    MinimalStream double: " << ms_d.str() << std::endl;
+        
+        assert(!ms_f.str().empty());
+        assert(!ms_d.str().empty());
+        
+        std::cout << "  ✓ Floating point types work correctly" << std::endl;
+    }
+    
+    // Performance Test: Compare std::ostringstream vs MinimalStream
+    {
+        const int iterations = 10000;
+        Xtruct x;
+        x.__set_string_thing("performance test string");
+        x.__set_byte_thing(123);
+        x.__set_i32_thing(456789);
+        x.__set_i64_thing(9876543210LL);
+        
+        // Test std::ostringstream performance
+        auto start_oss = std::chrono::high_resolution_clock::now();
+        std::string accumulated_result;  // Prevent optimization by accumulating results
+        for (int i = 0; i < iterations; ++i) {
+            std::ostringstream oss;
+            oss << x;
+            accumulated_result += oss.str();  // Use result to prevent optimization
+        }
+        auto end_oss = std::chrono::high_resolution_clock::now();
+        auto duration_oss = std::chrono::duration_cast<std::chrono::microseconds>(end_oss - start_oss).count();
+        
+        // Test MinimalStream performance
+        auto start_ms = std::chrono::high_resolution_clock::now();
+        accumulated_result.clear();  // Reuse for MinimalStream test
+        for (int i = 0; i < iterations; ++i) {
+            MinimalStream ms;
+            ms << x;
+            accumulated_result += ms.str();  // Use result to prevent optimization
+        }
+        auto end_ms = std::chrono::high_resolution_clock::now();
+        auto duration_ms = std::chrono::duration_cast<std::chrono::microseconds>(end_ms - start_ms).count();
+        
+        std::cout << "\n  Performance comparison (" << iterations << " iterations):" << std::endl;
+        std::cout << "    std::ostringstream: " << duration_oss << " μs" << std::endl;
+        std::cout << "    MinimalStream:      " << duration_ms << " μs" << std::endl;
+        
+        if (duration_ms < duration_oss) {
+            double improvement = ((double)(duration_oss - duration_ms) / duration_oss) * 100.0;
+            std::cout << "    MinimalStream is " << improvement << "% faster" << std::endl;
+        } else {
+            double difference = ((double)(duration_ms - duration_oss) / duration_oss) * 100.0;
+            std::cout << "    std::ostringstream is " << difference << "% faster" << std::endl;
+        }
+        
+        std::cout << "  ✓ Performance test completed" << std::endl;
+    }
+    
+    std::cout << "\n✅ All template_streamop tests passed!" << std::endl;
+    return 0;
+}