pw_string: Update docs & test operator<< overload

- Expand the documentation for overloading StringBuilder's operator<<
  for custom types. Move some documentation from code comments to .rst.
- Add tests for overloading operator<< for StringBuilder.

Change-Id: Ida9c9d8bdcc6aee645e38ef896b1eaf34b133d00
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/34800
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Paul Mathieu <paulmathieu@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_string/docs.rst b/pw_string/docs.rst
index 8944100..467908b 100644
--- a/pw_string/docs.rst
+++ b/pw_string/docs.rst
@@ -19,24 +19,15 @@
 =============
 C++17
 
-Dependencies
-============
-* ``pw_preprocessor``
-* ``pw_status``
-* ``pw_span``
-
-Features
-========
-
 pw::string::Format
-------------------
+==================
 The ``pw::string::Format`` and ``pw::string::FormatVaList`` functions provide
 safer alternatives to ``std::snprintf`` and ``std::vsnprintf``. The snprintf
 return value is awkward to interpret, and misinterpreting it can lead to serious
 bugs.
 
 Size report: replacing snprintf with pw::string::Format
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+-------------------------------------------------------
 The ``Format`` functions have a small, fixed code size cost. However, relative
 to equivalent ``std::snprintf`` calls, there is no incremental code size cost to
 using ``Format``.
@@ -44,28 +35,70 @@
 .. include:: format_size_report
 
 pw::StringBuilder
------------------
-StringBuilder facilitates building formatted strings in a fixed-size buffer. It
-is designed to give the flexibility of ``std::string`` and
-``std::ostringstream``, but with a small footprint. However, applications
-sensitive to code size should use StringBuilder with care.
+=================
+``pw::StringBuilder`` facilitates building formatted strings in a fixed-size
+buffer. It is designed to give the flexibility of ``std::string`` and
+``std::ostringstream``, but with a small footprint.
+
+Supporting custom types with StringBuilder
+------------------------------------------
+As with ``std::ostream``, StringBuilder supports printing custom types by
+overriding the ``<<`` operator. This is is done by defining ``operator<<`` in
+the same namespace as the custom type. For example:
+
+.. code-block:: cpp
+
+  namespace my_project {
+
+  struct MyType {
+    int foo;
+    const char* bar;
+  };
+
+  pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value) {
+    return sb << "MyType(" << value.foo << ", " << value.bar << ')';
+  }
+
+  }  // namespace my_project
+
+Internally, ``StringBuilder`` uses the ``ToString`` function to print. The
+``ToString`` template function can be specialized to support custom types with
+``StringBuilder``, though it is recommended to overload ``operator<<`` instead.
+This example shows how to specialize ``pw::ToString``:
+
+.. code-block:: cpp
+
+  #include "pw_string/to_string.h"
+
+  namespace pw {
+
+  template <>
+  StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) {
+    return CopyString(MyStatusString(value), buffer);
+  }
+
+  }  // namespace pw
 
 Size report: replacing snprintf with pw::StringBuilder
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+------------------------------------------------------
+StringBuilder is safe, flexible, and results in much smaller code size than
+using ``std::ostringstream``. However, applications sensitive to code size
+should use StringBuilder with care.
+
 The fixed code size cost of StringBuilder is significant, though smaller than
 ``std::snprintf``. Using StringBuilder's << and append methods exclusively in
 place of ``snprintf`` reduces code size, but ``snprintf`` may be difficult to
 avoid.
 
 The incremental code size cost of StringBuilder is comparable to ``snprintf`` if
-errors are handled. Each argument to StringBuilder's << expands to a function
-call, but one or two StringBuilder appends may have a smaller code size impact
-than a single ``snprintf`` call.
+errors are handled. Each argument to StringBuilder's ``<<`` expands to a
+function call, but one or two StringBuilder appends may have a smaller code size
+impact than a single ``snprintf`` call.
 
 .. include:: string_builder_size_report
 
 Future work
-^^^^^^^^^^^
+===========
 * StringBuilder's fixed size cost can be dramatically reduced by limiting
   support for 64-bit integers.
 * Consider integrating with the tokenizer module.
diff --git a/pw_string/public/pw_string/string_builder.h b/pw_string/public/pw_string/string_builder.h
index 7d2c5a4..47db98f 100644
--- a/pw_string/public/pw_string/string_builder.h
+++ b/pw_string/public/pw_string/string_builder.h
@@ -41,12 +41,25 @@
 // StringBuilder supports C++-style << output, similar to std::ostringstream. It
 // also supports std::string-like append functions and printf-style output.
 //
-// StringBuilder uses the ToString function to support arbitrary types. Defining
-// a ToString template specialization overload in the pw namespace allows
-// writing that type to a StringBuilder with <<.
+// Support for custom types is added by overloading operator<< in the same
+// namespace as the custom type. For example:
 //
-// For example, the following ToString overload allows writing MyStatus objects
-// to StringBuilders:
+//   namespace my_project {
+//
+//   struct MyType {
+//     int foo;
+//     const char* bar;
+//   };
+//
+//   pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value) {
+//     return sb << "MyType(" << value.foo << ", " << value.bar << ')';
+//   }
+//
+//   }  // namespace my_project
+//
+// The ToString template function can be specialized to support custom types
+// with StringBuilder, though overloading operator<< is generally preferred. For
+// example:
 //
 //   namespace pw {
 //
@@ -57,32 +70,6 @@
 //
 //   }  // namespace pw
 //
-// For complex types, it may be easier to override StringBuilder's << operator,
-// similar to the standard library's std::ostream. For example:
-//
-//   namespace pw {
-//
-//   StringBuilder& operator<<(StringBuilder& sb, const MyType& value) {
-//     return sb << "MyType(" << value.foo << ", " << value.bar << ')';
-//   }
-//
-//   }  // namespace pw
-//
-// Alternately, complex types may use a StringBuilder in their ToString, but it
-// is likely to be simpler to override StringBuilder's operator<<.
-//
-// StringBuilder is safe, flexible, and results in much smaller code size than
-// using std::ostringstream. However, applications sensitive to code size should
-// use StringBuilder with care.
-//
-// The fixed code size cost of StringBuilder is significant, though smaller than
-// std::snprintf. Using StringBuilder's << and append methods exclusively in
-// place of snprintf reduces code size, but snprintf may be difficult to avoid.
-//
-// The incremental code size cost of StringBuilder is comparable to snprintf if
-// errors are handled. Each argument to StringBuilder's << expands to a function
-// call, but one or two StringBuilder appends may have a smaller code size
-// impact than a single snprintf call. See the size report for further analysis.
 class StringBuilder {
  public:
   // Creates an empty StringBuilder.
@@ -282,7 +269,7 @@
 //   str.c_str();  // null terminated C string "The answer is 42."
 //   str.view();   // std::string_view of "The answer is 42."
 //
-template <size_t kSizeBytes>
+template <size_t size_bytes>
 class StringBuffer : public StringBuilder {
  public:
   StringBuffer() : StringBuilder(buffer_) {}
@@ -308,7 +295,7 @@
   }
 
   StringBuffer& operator=(const StringBuffer& other) {
-    assign<kSizeBytes>(other);
+    assign<size_bytes>(other);
     return *this;
   }
 
@@ -322,9 +309,9 @@
   }
 
   // Returns the maximum length of the string, excluding the null terminator.
-  static constexpr size_t max_size() { return kSizeBytes - 1; }
+  static constexpr size_t max_size() { return size_bytes - 1; }
 
-  // Returns a StringBuffer<kSizeBytes>& instead of a generic StringBuilder& for
+  // Returns a StringBuffer<size_bytes>& instead of a generic StringBuilder& for
   // append calls and stream-style operations.
   template <typename... Args>
   StringBuffer& append(Args&&... args) {
@@ -344,8 +331,8 @@
     std::memcpy(buffer_, other.data(), other.size() + 1);  // include the \0
   }
 
-  static_assert(kSizeBytes >= 1u, "StringBuffers must be at least 1 byte long");
-  char buffer_[kSizeBytes];
+  static_assert(size_bytes >= 1u, "StringBuffers must be at least 1 byte long");
+  char buffer_[size_bytes];
 };
 
 namespace string_internal {
diff --git a/pw_string/string_builder_test.cc b/pw_string/string_builder_test.cc
index 22378cc..ee8bad5 100644
--- a/pw_string/string_builder_test.cc
+++ b/pw_string/string_builder_test.cc
@@ -582,5 +582,42 @@
               25);
 static_assert(DefaultStringBufferSize('a', nullptr, 'b', 4, 5, 6, 7, 8) == 33);
 
+struct SomeCustomType {};
+
+StringBuilder& operator<<(StringBuilder& sb, const SomeCustomType&) {
+  return sb << "SomeCustomType was here!";
+}
+
+TEST(StringBuilder, ShiftOperatorOverload_SameNamsepace) {
+  pw::StringBuffer<48> buffer;
+  buffer << SomeCustomType{};
+
+  EXPECT_STREQ("SomeCustomType was here!", buffer.c_str());
+}
+
 }  // namespace
 }  // namespace pw
+
+namespace some_other_ns {
+
+struct MyCustomType {
+  int item;
+};
+
+pw::StringBuilder& operator<<(pw::StringBuilder& sb,
+                              const MyCustomType& value) {
+  return sb << "MyCustomType(" << value.item << ')';
+}
+
+}  // namespace some_other_ns
+
+namespace pw_test_namespace {
+
+TEST(StringBuilder, ShiftOperatorOverload_DifferentNamsepace) {
+  pw::StringBuffer<48> buffer;
+  buffer << "This is " << some_other_ns::MyCustomType{1138};
+
+  EXPECT_STREQ("This is MyCustomType(1138)", buffer.data());
+}
+
+}  // namespace pw_test_namespace