THRIFT-2067 C++: all generated objects provide ostream operator<<
diff --git a/lib/cpp/Makefile.am b/lib/cpp/Makefile.am
index ab9108a..4bd40fb 100755
--- a/lib/cpp/Makefile.am
+++ b/lib/cpp/Makefile.am
@@ -131,7 +131,8 @@
                          src/thrift/TProcessor.h \
                          src/thrift/TApplicationException.h \
                          src/thrift/TLogging.h \
-                         src/thrift/cxxfunctional.h
+                         src/thrift/cxxfunctional.h \
+                         src/thrift/TToString.h
 
 include_concurrencydir = $(include_thriftdir)/concurrency
 include_concurrency_HEADERS = \
diff --git a/lib/cpp/src/thrift/TToString.h b/lib/cpp/src/thrift/TToString.h
new file mode 100644
index 0000000..c160e09
--- /dev/null
+++ b/lib/cpp/src/thrift/TToString.h
@@ -0,0 +1,89 @@
+/*
+ * 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_TOSTRING_H_
+#define _THRIFT_TOSTRING_H_ 1
+
+#include <boost/lexical_cast.hpp>
+
+#include <vector>
+#include <map>
+#include <set>
+#include <string>
+#include <sstream>
+
+namespace apache { namespace thrift {
+
+template <typename T>
+std::string to_string(const T& t) {
+  return boost::lexical_cast<std::string>(t);
+}
+
+template <typename K, typename V>
+std::string to_string(const std::map<K, V>& m);
+
+template <typename T>
+std::string to_string(const std::set<T>& s);
+
+template <typename T>
+std::string to_string(const std::vector<T>& t);
+
+template <typename K, typename V>
+std::string to_string(const typename std::pair<K, V>& v) {
+  std::ostringstream o;
+  o << to_string(v.first) << ": " << to_string(v.second);
+  return o.str();
+}
+
+template <typename T>
+std::string to_string(const T& beg, const T& end)
+{
+  std::ostringstream o;
+  for (T it = beg; it != end; ++it) {
+    if (it != beg)
+      o << ", ";
+    o << to_string(*it);
+  }
+  return o.str();
+}
+
+template <typename T>
+std::string to_string(const std::vector<T>& t) {
+  std::ostringstream o;
+  o << "[" << to_string(t.begin(), t.end()) << "]";
+  return o.str();
+}
+
+template <typename K, typename V>
+std::string to_string(const std::map<K, V>& m) {
+  std::ostringstream o;
+  o << "{" << to_string(m.begin(), m.end()) << "}";
+  return o.str();
+}
+
+template <typename T>
+std::string to_string(const std::set<T>& s) {
+  std::ostringstream o;
+  o << "{" << to_string(s.begin(), s.end()) << "}";
+  return o.str();
+}
+
+}} // apache::thrift
+
+#endif // _THRIFT_TOSTRING_H_
diff --git a/lib/cpp/test/Makefile.am b/lib/cpp/test/Makefile.am
index 6779ac6..c1fad3e 100755
--- a/lib/cpp/test/Makefile.am
+++ b/lib/cpp/test/Makefile.am
@@ -82,7 +82,8 @@
 	UnitTestMain.cpp \
 	TMemoryBufferTest.cpp \
 	TBufferBaseTest.cpp \
-	Base64Test.cpp
+	Base64Test.cpp \
+	ToStringTest.cpp
 
 if !WITH_BOOSTTHREADS
 UnitTests_SOURCES += \
diff --git a/lib/cpp/test/ToStringTest.cpp b/lib/cpp/test/ToStringTest.cpp
new file mode 100644
index 0000000..1a89c11
--- /dev/null
+++ b/lib/cpp/test/ToStringTest.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 <vector>
+#include <map>
+
+#include <boost/test/auto_unit_test.hpp>
+
+#include <thrift/TToString.h>
+
+#include "gen-cpp/ThriftTest_types.h"
+#include "gen-cpp/OptionalRequiredTest_types.h"
+#include "gen-cpp/DebugProtoTest_types.h"
+
+using apache::thrift::to_string;
+
+BOOST_AUTO_TEST_SUITE( ToStringTest )
+
+BOOST_AUTO_TEST_CASE( base_types_to_string ) {
+  BOOST_CHECK_EQUAL(to_string(10), "10");
+  BOOST_CHECK_EQUAL(to_string(true), "1");
+  BOOST_CHECK_EQUAL(to_string('a'), "a");
+  BOOST_CHECK_EQUAL(to_string(1.2), "1.2");
+  BOOST_CHECK_EQUAL(to_string("abc"), "abc");
+}
+
+BOOST_AUTO_TEST_CASE( empty_vector_to_string ) {
+  std::vector<int> l;
+  BOOST_CHECK_EQUAL(to_string(l), "[]");
+}
+
+BOOST_AUTO_TEST_CASE( single_item_vector_to_string ) {
+  std::vector<int> l;
+  l.push_back(100);
+  BOOST_CHECK_EQUAL(to_string(l), "[100]");
+}
+
+BOOST_AUTO_TEST_CASE( multiple_item_vector_to_string ) {
+  std::vector<int> l;
+  l.push_back(100);
+  l.push_back(150);
+  BOOST_CHECK_EQUAL(to_string(l), "[100, 150]");
+}
+
+BOOST_AUTO_TEST_CASE( empty_map_to_string ) {
+  std::map<int, std::string> m;
+  BOOST_CHECK_EQUAL(to_string(m), "{}");
+}
+
+BOOST_AUTO_TEST_CASE( single_item_map_to_string ) {
+  std::map<int, std::string> m;
+  m[12] = "abc";
+  BOOST_CHECK_EQUAL(to_string(m), "{12: abc}");
+}
+
+BOOST_AUTO_TEST_CASE( multi_item_map_to_string ) {
+  std::map<int, std::string> m;
+  m[12] = "abc";
+  m[31] = "xyz";
+  BOOST_CHECK_EQUAL(to_string(m), "{12: abc, 31: xyz}");
+}
+
+BOOST_AUTO_TEST_CASE( empty_set_to_string ) {
+  std::set<char> s;
+  BOOST_CHECK_EQUAL(to_string(s), "{}");
+}
+
+BOOST_AUTO_TEST_CASE( single_item_set_to_string ) {
+  std::set<char> s;
+  s.insert('c');
+  BOOST_CHECK_EQUAL(to_string(s), "{c}");
+}
+
+BOOST_AUTO_TEST_CASE( multi_item_set_to_string ) {
+  std::set<char> s;
+  s.insert('a');
+  s.insert('z');
+  BOOST_CHECK_EQUAL(to_string(s), "{a, z}");
+}
+
+BOOST_AUTO_TEST_CASE( generated_empty_object_to_string ) {
+  thrift::test::EmptyStruct e;
+  BOOST_CHECK_EQUAL(to_string(e), "EmptyStruct()");
+}
+
+BOOST_AUTO_TEST_CASE( generated_single_basic_field_object_to_string ) {
+  thrift::test::StructA a;
+  a.__set_s("abcd");
+  BOOST_CHECK_EQUAL(to_string(a), "StructA(s=abcd)");
+}
+
+BOOST_AUTO_TEST_CASE( generated_two_basic_fields_object_to_string ) {
+  thrift::test::Bonk a;
+  a.__set_message("abcd");
+  a.__set_type(1234);
+  BOOST_CHECK_EQUAL(to_string(a), "Bonk(message=abcd, type=1234)");
+}
+
+BOOST_AUTO_TEST_CASE( generated_optional_fields_object_to_string ) {
+  thrift::test::Tricky2 a;
+  BOOST_CHECK_EQUAL(to_string(a), "Tricky2(im_optional=<null>)");
+  a.__set_im_optional(123);
+  BOOST_CHECK_EQUAL(to_string(a), "Tricky2(im_optional=123)");
+}
+
+BOOST_AUTO_TEST_CASE( generated_nested_object_to_string ) {
+  thrift::test::OneField a;
+  BOOST_CHECK_EQUAL(to_string(a), "OneField(field=EmptyStruct())");
+}
+
+BOOST_AUTO_TEST_CASE( generated_nested_list_object_to_string ) {
+  thrift::test::ListBonks l;
+  l.bonk.assign(2, thrift::test::Bonk());
+  l.bonk[0].__set_message("a");
+  l.bonk[1].__set_message("b");
+
+  BOOST_CHECK_EQUAL(to_string(l),
+                    "ListBonks(bonk=[Bonk(message=a, type=0), Bonk(message=b, type=0)])");
+}
+
+BOOST_AUTO_TEST_SUITE_END()