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/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;
 }