pw_span: Move pw_string, pw_unit_test to std::span

- Update pw_string and the test framework to std::span.
- Have pw_string pass std::span by value, for consistency with other
  uses in Pigweed.
- Recommend using template specializations instead of overloads for
  custom ToString implementations. Template specializations are
  preferred because the main ToString definition is a template.

Change-Id: Ib67c6bce1752c4a90e2138bdb1f20c6671f55d50
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/12841
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/pw_kvs/key_value_store_map_test.cc b/pw_kvs/key_value_store_map_test.cc
index ac6a83b..9919cae 100644
--- a/pw_kvs/key_value_store_map_test.cc
+++ b/pw_kvs/key_value_store_map_test.cc
@@ -13,13 +13,25 @@
 // the License.
 
 #include <cstdlib>
-#include <random>
 #include <set>
+#include <span>
 #include <string>
 #include <string_view>
 #include <unordered_map>
 #include <unordered_set>
 
+// TODO(hepler): Clang 11 fails to compile this file if <random> is included
+// before <span>. It seems to miss the definition of std::span. This compiles
+// correctly in GCC 9.
+//
+//   In file included from ../pw_kvs/key_value_store_map_test.cc:33:
+//   In file included from ../pw_unit_test/public_overrides/gtest/gtest.h:20:
+//   In file included from ../pw_unit_test/public/pw_unit_test/framework.h:32:
+//   ../pw_string/public/pw_string/string_builder.h:267:25: error:
+//       implicit instantiation of undefined template
+//       'std::span<char, 18446744073709551615>'
+#include <random>
+
 #define DUMP_KVS_CONTENTS 0
 
 #if DUMP_KVS_CONTENTS
@@ -33,7 +45,6 @@
 #include "pw_kvs/internal/entry.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_log/log.h"
-#include "pw_span/span.h"
 
 namespace pw::kvs {
 namespace {
@@ -249,7 +260,7 @@
 
         char value[kMaxValueLength + 1] = {};
         EXPECT_EQ(Status::OK,
-                  item.Get(as_writable_bytes(span(value))).status());
+                  item.Get(std::as_writable_bytes(std::span(value))).status());
         EXPECT_EQ(map_entry->second, std::string(value));
       }
     }
@@ -262,7 +273,7 @@
     StartOperation("Put", key);
     EXPECT_LE(value.size(), kMaxValueLength);
 
-    Status result = kvs_.Put(key, as_bytes(span(value)));
+    Status result = kvs_.Put(key, std::as_bytes(std::span(value)));
 
     if (key.empty() || key.size() > internal::Entry::kMaxKeyLength) {
       EXPECT_EQ(Status::INVALID_ARGUMENT, result);
diff --git a/pw_string/format.cc b/pw_string/format.cc
index b069fe8..c9ed379 100644
--- a/pw_string/format.cc
+++ b/pw_string/format.cc
@@ -18,7 +18,7 @@
 
 namespace pw::string {
 
-StatusWithSize Format(const span<char>& buffer, const char* format, ...) {
+StatusWithSize Format(std::span<char> buffer, const char* format, ...) {
   va_list args;
   va_start(args, format);
   const StatusWithSize result = FormatVaList(buffer, format, args);
@@ -27,7 +27,7 @@
   return result;
 }
 
-StatusWithSize FormatVaList(const span<char>& buffer,
+StatusWithSize FormatVaList(std::span<char> buffer,
                             const char* format,
                             va_list args) {
   if (buffer.empty()) {
diff --git a/pw_string/format_test.cc b/pw_string/format_test.cc
index ad7e39e..dfaac7b 100644
--- a/pw_string/format_test.cc
+++ b/pw_string/format_test.cc
@@ -15,9 +15,9 @@
 #include "pw_string/format.h"
 
 #include <cstdarg>
+#include <span>
 
 #include "gtest/gtest.h"
-#include "pw_span/span.h"
 
 namespace pw::string {
 namespace {
@@ -41,7 +41,7 @@
 }
 
 TEST(Format, EmptyBuffer_ReturnsResourceExhausted) {
-  auto result = Format(span<char>(), "?");
+  auto result = Format(std::span<char>(), "?");
 
   EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
   EXPECT_EQ(0u, result.size());
@@ -65,7 +65,9 @@
   EXPECT_STREQ("2big", buffer);
 }
 
-StatusWithSize CallFormatWithVaList(span<char> buffer, const char* fmt, ...) {
+StatusWithSize CallFormatWithVaList(std::span<char> buffer,
+                                    const char* fmt,
+                                    ...) {
   va_list args;
   va_start(args, fmt);
 
diff --git a/pw_string/public/pw_string/format.h b/pw_string/public/pw_string/format.h
index 72198bf..5c0adfe 100644
--- a/pw_string/public/pw_string/format.h
+++ b/pw_string/public/pw_string/format.h
@@ -22,9 +22,9 @@
 // the null terminator.
 
 #include <cstdarg>
+#include <span>
 
 #include "pw_preprocessor/compiler.h"
-#include "pw_span/span.h"
 #include "pw_status/status_with_size.h"
 
 namespace pw::string {
@@ -40,12 +40,12 @@
 //   Status::INVALID_ARGUMENT if there was a formatting error.
 //
 PW_PRINTF_FORMAT(2, 3)
-StatusWithSize Format(const span<char>& buffer, const char* format, ...);
+StatusWithSize Format(std::span<char> buffer, const char* format, ...);
 
 // Writes a printf-style formatted string with va_list-packed arguments to the
 // provided buffer, similarly to std::vsnprintf. The return value is the same as
 // above.
-StatusWithSize FormatVaList(const span<char>& buffer,
+StatusWithSize FormatVaList(std::span<char> buffer,
                             const char* format,
                             va_list args);
 
diff --git a/pw_string/public/pw_string/string_builder.h b/pw_string/public/pw_string/string_builder.h
index 1204e71..96a88ef 100644
--- a/pw_string/public/pw_string/string_builder.h
+++ b/pw_string/public/pw_string/string_builder.h
@@ -17,12 +17,12 @@
 #include <cstdarg>
 #include <cstddef>
 #include <cstring>
+#include <span>
 #include <string_view>
 #include <type_traits>
 #include <utility>
 
 #include "pw_preprocessor/compiler.h"
-#include "pw_span/span.h"
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
 #include "pw_string/to_string.h"
@@ -42,30 +42,31 @@
 // also supports std::string-like append functions and printf-style output.
 //
 // StringBuilder uses the ToString function to support arbitrary types. Defining
-// a ToString overload in the pw::string namespace allows writing that type to a
-// StringBuilder with <<.
+// a ToString template specialization overload in the pw namespace allows
+// writing that type to a StringBuilder with <<.
 //
 // For example, the following ToString overload allows writing MyStatus objects
 // to StringBuilders:
 //
-//   namespace pw::string {
+//   namespace pw {
 //
-//   StatusWithSize ToString(MyStatus value, const span<char>& buffer) {
+//   template <>
+//   StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) {
 //     return CopyString(MyStatusString(value), buffer);
 //   }
 //
-//   }  // namespace pw::string
+//   }  // 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::string {
+//   namespace pw {
 //
 //   StringBuilder& operator<<(StringBuilder& sb, const MyType& value) {
 //     return sb << "MyType(" << value.foo << ", " << value.bar << ')';
 //   }
 //
-//   }  // namespace pw::string
+//   }  // namespace pw
 //
 // Alternately, complex types may use a StringBuilder in their ToString, but it
 // is likely to be simpler to override StringBuilder's operator<<.
@@ -85,8 +86,7 @@
 class StringBuilder {
  public:
   // Creates an empty StringBuilder.
-  constexpr StringBuilder(const span<char>& buffer)
-      : buffer_(buffer), size_(0) {
+  constexpr StringBuilder(std::span<char> buffer) : buffer_(buffer), size_(0) {
     NullTerminate();
   }
 
@@ -108,9 +108,9 @@
   // passed into functions that take a std::string_view.
   operator std::string_view() const { return view(); }
 
-  // Returns a span<const std::byte> representation of this StringBuffer.
-  span<const std::byte> as_bytes() const {
-    return span(reinterpret_cast<const std::byte*>(buffer_.data()), size_);
+  // Returns a std::span<const std::byte> representation of this StringBuffer.
+  std::span<const std::byte> as_bytes() const {
+    return std::span(reinterpret_cast<const std::byte*>(buffer_.data()), size_);
   }
 
   // Returns the StringBuilder's status, which reflects the most recent error
@@ -242,7 +242,7 @@
 
  protected:
   // Functions to support StringBuffer copies.
-  constexpr StringBuilder(const span<char>& buffer, const StringBuilder& other)
+  constexpr StringBuilder(std::span<char> buffer, const StringBuilder& other)
       : buffer_(buffer),
         size_(other.size_),
         status_(other.status_),
@@ -263,7 +263,7 @@
 
   void SetErrorStatus(Status status);
 
-  const span<char> buffer_;
+  const std::span<char> buffer_;
 
   size_t size_;
   Status status_;
diff --git a/pw_string/public/pw_string/to_string.h b/pw_string/public/pw_string/to_string.h
index fde6a69..463005f 100644
--- a/pw_string/public/pw_string/to_string.h
+++ b/pw_string/public/pw_string/to_string.h
@@ -21,8 +21,8 @@
 // buffer has room.
 //
 // ToString functions may be defined for any type. This is done by providing a
-// ToString overload in the pw namespace. The overload must follow ToString's
-// semantics:
+// ToString template specialization in the pw namespace. The specialization must
+// follow ToString's semantics:
 //
 //   1. Always null terminate if the output buffer has room.
 //   2. Return the number of characters written, excluding the null terminator,
@@ -31,13 +31,14 @@
 //      with the number of characters written and a status of
 //      RESOURCE_EXHAUSTED. Other status codes may be used for different errors.
 //
-// For example, providing the following overload would allow ToString, and any
-// classes that use it, to print instances of a custom type:
+// For example, providing the following specialization would allow ToString, and
+// any classes that use it, to print instances of a custom type:
 //
 //   namespace pw {
 //
-//   inline StatusWithSize ToString(const SomeCustomType& value,
-//                                  const std::span<char>& buffer) {
+//   template <>
+//   StatusWithSize ToString<SomeCustomType>(const SomeCustomType& value,
+//                           std::span<char> buffer) {
 //     return /* ... implementation ... */;
 //   }
 //
@@ -51,10 +52,10 @@
 // StringBuilder may be easier to work with. StringBuilder's operator<< may be
 // overloaded for custom types.
 
+#include <span>
 #include <string_view>
 #include <type_traits>
 
-#include "pw_span/span.h"
 #include "pw_status/status.h"
 #include "pw_string/type_to_string.h"
 
@@ -63,7 +64,7 @@
 // This function provides string printing numeric types, enums, and anything
 // that convertible to a std::string_view, such as std::string.
 template <typename T>
-StatusWithSize ToString(const T& value, const span<char>& buffer) {
+StatusWithSize ToString(const T& value, std::span<char> buffer) {
   if constexpr (std::is_same_v<std::remove_cv_t<T>, bool>) {
     return string::BoolToString(value, buffer);
   } else if constexpr (std::is_same_v<std::remove_cv_t<T>, char>) {
@@ -85,17 +86,18 @@
   }
 }
 
-// ToString overloads for custom types may be provided.
-inline StatusWithSize ToString(Status status, const span<char>& buffer) {
+// ToString overloads for Pigweed types. To override ToString for a custom type,
+// specialize the ToString template function.
+inline StatusWithSize ToString(Status status, std::span<char> buffer) {
   return string::CopyString(status.str(), buffer);
 }
 
-inline StatusWithSize ToString(pw_Status status, const span<char>& buffer) {
+inline StatusWithSize ToString(pw_Status status, std::span<char> buffer) {
   return ToString(Status(status), buffer);
 }
 
-inline StatusWithSize ToString(std::byte byte, const span<char>& buffer) {
-  return string::IntToHexString(static_cast<uint64_t>(byte), buffer);
+inline StatusWithSize ToString(std::byte byte, std::span<char> buffer) {
+  return string::IntToHexString(static_cast<unsigned>(byte), buffer);
 }
 
 }  // namespace pw
diff --git a/pw_string/public/pw_string/type_to_string.h b/pw_string/public/pw_string/type_to_string.h
index ae646ff..147badb 100644
--- a/pw_string/public/pw_string/type_to_string.h
+++ b/pw_string/public/pw_string/type_to_string.h
@@ -18,10 +18,10 @@
 // in "pw_string/to_string.h" should be used instead of these functions.
 
 #include <cstdint>
+#include <span>
 #include <string_view>
 #include <type_traits>
 
-#include "pw_span/span.h"
 #include "pw_status/status_with_size.h"
 
 namespace pw::string {
@@ -55,7 +55,7 @@
 //      sites pass their arguments directly and casting instructions are shared.
 //
 template <typename T>
-StatusWithSize IntToString(T value, const span<char>& buffer) {
+StatusWithSize IntToString(T value, std::span<char> buffer) {
   if constexpr (std::is_signed_v<T>) {
     return IntToString<int64_t>(value, buffer);
   } else {
@@ -64,14 +64,14 @@
 }
 
 template <>
-StatusWithSize IntToString(uint64_t value, const span<char>& buffer);
+StatusWithSize IntToString(uint64_t value, std::span<char> buffer);
 
 template <>
-StatusWithSize IntToString(int64_t value, const span<char>& buffer);
+StatusWithSize IntToString(int64_t value, std::span<char> buffer);
 
 // Writes an integer as a hexadecimal string. Semantics match IntToString. The
 // output is lowercase without a leading 0x.
-StatusWithSize IntToHexString(uint64_t value, const span<char>& buffer);
+StatusWithSize IntToHexString(uint64_t value, std::span<char> buffer);
 
 // Rounds a floating point number to an integer and writes it as a
 // null-terminated string. Returns the number of characters written, excluding
@@ -92,17 +92,17 @@
 //   FloatAsIntToString(INFINITY, buffer) -> writes "-inf" to the buffer
 //   FloatAsIntToString(-NAN, buffer)     -> writes "-NaN" to the buffer
 //
-StatusWithSize FloatAsIntToString(float value, const span<char>& buffer);
+StatusWithSize FloatAsIntToString(float value, std::span<char> buffer);
 
 // Writes a bool as "true" or "false". Semantics match CopyEntireString.
-StatusWithSize BoolToString(bool value, const span<char>& buffer);
+StatusWithSize BoolToString(bool value, std::span<char> buffer);
 
 // String used to represent null pointers.
 inline constexpr std::string_view kNullPointerString("(null)");
 
 // Writes the pointer's address or kNullPointerString. Semantics match
 // CopyEntireString.
-StatusWithSize PointerToString(const void* pointer, const span<char>& buffer);
+StatusWithSize PointerToString(const void* pointer, std::span<char> buffer);
 
 // Copies the string to the buffer, truncating if the full string does not fit.
 // Always null terminates if buffer.size() > 0.
@@ -110,9 +110,9 @@
 // Returns the number of characters written, excluding the null terminator. If
 // the string is truncated, the status is RESOURCE_EXHAUSTED.
 StatusWithSize CopyString(const std::string_view& value,
-                          const span<char>& buffer);
+                          std::span<char> buffer);
 
-inline StatusWithSize CopyString(const char* value, const span<char>& buffer) {
+inline StatusWithSize CopyString(const char* value, std::span<char> buffer) {
   if (value == nullptr) {
     return PointerToString(value, buffer);
   }
@@ -126,10 +126,10 @@
 // the full string does not fit, only a null terminator is written and the
 // status is RESOURCE_EXHAUSTED.
 StatusWithSize CopyEntireString(const std::string_view& value,
-                                const span<char>& buffer);
+                                std::span<char> buffer);
 
 inline StatusWithSize CopyEntireString(const char* value,
-                                       const span<char>& buffer) {
+                                       std::span<char> buffer) {
   if (value == nullptr) {
     return PointerToString(value, buffer);
   }
@@ -144,6 +144,6 @@
 // printing for unknown types, if desired. Implementations must follow the
 // ToString semantics.
 template <typename T>
-StatusWithSize UnknownTypeToString(const T& value, const span<char>& buffer);
+StatusWithSize UnknownTypeToString(const T& value, std::span<char> buffer);
 
 }  // namespace pw::string
diff --git a/pw_string/size_report/format_many_without_error_handling.cc b/pw_string/size_report/format_many_without_error_handling.cc
index 9d2df74..357fc2a 100644
--- a/pw_string/size_report/format_many_without_error_handling.cc
+++ b/pw_string/size_report/format_many_without_error_handling.cc
@@ -43,7 +43,7 @@
 
 void OutputStringsToBuffer() {
 #if USE_FORMAT
-  auto buffer = pw::span(get_buffer, get_size);
+  auto buffer = std::span(get_buffer, get_size);
 #else
   char* buffer = get_buffer;
   unsigned buffer_size = get_size;
diff --git a/pw_string/size_report/format_multiple.cc b/pw_string/size_report/format_multiple.cc
index 1b16a9c..b27df5a 100644
--- a/pw_string/size_report/format_multiple.cc
+++ b/pw_string/size_report/format_multiple.cc
@@ -31,7 +31,7 @@
 #include "pw_string/format.h"
 
 #define FORMAT_FUNCTION(...) \
-  pw::string::Format(span(buffer, buffer_size - string_size), __VA_ARGS__)
+  pw::string::Format(std::span(buffer, buffer_size - string_size), __VA_ARGS__)
 #define CHECK_RESULT(result) ProcessResult(&string_size, result)
 
 namespace {
diff --git a/pw_string/size_report/format_single.cc b/pw_string/size_report/format_single.cc
index 04f9356..94afe76 100644
--- a/pw_string/size_report/format_single.cc
+++ b/pw_string/size_report/format_single.cc
@@ -40,7 +40,10 @@
 
 #if USE_FORMAT
   // The code for using pw::string::Format is much simpler and safer.
-  return Format(span(buffer, buffer_size), "hello %s %d", get_buffer, get_size)
+  return Format(std::span(buffer, buffer_size),
+                "hello %s %d",
+                get_buffer,
+                get_size)
       .size();
 #else  // std::snprintf
   if (buffer_size == 0u) {
diff --git a/pw_string/size_report/string_builder_size_report_incremental.cc b/pw_string/size_report/string_builder_size_report_incremental.cc
index 87e7da9..a37fdbe 100644
--- a/pw_string/size_report/string_builder_size_report_incremental.cc
+++ b/pw_string/size_report/string_builder_size_report_incremental.cc
@@ -87,7 +87,7 @@
 
   ProcessResult(buffer, &bytes, size, result);
 
-  pw::StringBuilder sb(pw::span(buffer, size));
+  pw::StringBuilder sb(std::span(buffer, size));
   sb << "This is part of the base " << 123 << false << '\n';
 
   sb.clear();
diff --git a/pw_string/string_builder_test.cc b/pw_string/string_builder_test.cc
index ccbbcae..c36c748 100644
--- a/pw_string/string_builder_test.cc
+++ b/pw_string/string_builder_test.cc
@@ -12,18 +12,19 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_string//string_builder.h"
+#include "pw_string/string_builder.h"
 
 #include <cinttypes>
 #include <cmath>
 #include <cstdint>
 #include <cstring>
+#include <span>
 #include <string_view>
 
 #include "gtest/gtest.h"
 #include "pw_string/format.h"
 
-namespace {
+namespace this_pw_test {
 
 struct CustomType {
   uint32_t a;
@@ -38,18 +39,25 @@
   CustomType& operator=(const CustomType&) = delete;
 };
 
-}  // namespace
+}  // namespace this_pw_test
 
 namespace pw {
 
-StatusWithSize ToString(const ::CustomType&, const span<char>& buffer) {
-  return string::Format(buffer, ::CustomType::kToString);
+template <>
+StatusWithSize ToString<this_pw_test::CustomType>(
+    const this_pw_test::CustomType&, std::span<char> buffer) {
+  return string::Format(buffer, this_pw_test::CustomType::kToString);
 }
 
+}  // namespace pw
+
+namespace pw {
 namespace {
 
+using this_pw_test::CustomType;
+
 TEST(StringBuilder, EmptyBuffer_SizeAndMaxSizeAreCorrect) {
-  StringBuilder sb(span<char>{});
+  StringBuilder sb(std::span<char>{});
 
   EXPECT_TRUE(sb.empty());
   EXPECT_EQ(0u, sb.size());
@@ -64,7 +72,7 @@
   char buffer[kNoTouch.size()];
   std::memcpy(buffer, kNoTouch.data(), sizeof(buffer));
 
-  StringBuilder sb(span(buffer, 0));
+  StringBuilder sb(std::span(buffer, 0));
 
   sb << CustomType() << " is " << 12345;
   EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
@@ -75,7 +83,7 @@
   char buffer[kNoTouch.size()];
   std::memcpy(buffer, kNoTouch.data(), sizeof(buffer));
 
-  StringBuilder sb(span(buffer, 0));
+  StringBuilder sb(std::span(buffer, 0));
 
   EXPECT_FALSE(sb.append("Hello").ok());
   EXPECT_EQ(kNoTouch, std::string_view(buffer, sizeof(buffer)));
@@ -85,7 +93,7 @@
   char buffer[kNoTouch.size()];
   std::memcpy(buffer, kNoTouch.data(), sizeof(buffer));
 
-  StringBuilder sb(span(buffer, 0));
+  StringBuilder sb(std::span(buffer, 0));
 
   sb.resize(0);
   EXPECT_TRUE(sb.ok());
@@ -93,7 +101,7 @@
 }
 
 TEST(StringBuilder, EmptyBuffer_AppendEmpty_ResourceExhausted) {
-  StringBuilder sb(span<char>{});
+  StringBuilder sb(std::span<char>{});
   EXPECT_EQ(Status::OK, sb.last_status());
   EXPECT_EQ(Status::OK, sb.status());
 
@@ -424,6 +432,14 @@
   EXPECT_EQ(Status::RESOURCE_EXHAUSTED, two.status());
 }
 
+TEST(StringBuilder, Object) {
+  StringBuffer<64> sb;
+  sb << CustomType();
+
+  EXPECT_STREQ(CustomType::kToString, sb.data());
+  EXPECT_EQ(std::strlen(CustomType::kToString), sb.size());
+}
+
 TEST(MakeString, Object) {
   CustomType custom;
   const auto sb = MakeString<64>(custom);
diff --git a/pw_string/to_string_test.cc b/pw_string/to_string_test.cc
index 1c25023..6f4a4da 100644
--- a/pw_string/to_string_test.cc
+++ b/pw_string/to_string_test.cc
@@ -39,7 +39,7 @@
   CustomType& operator=(const CustomType&) = delete;
 };
 
-StatusWithSize ToString(const CustomType&, const span<char>& buffer) {
+StatusWithSize ToString(const CustomType&, std::span<char> buffer) {
   int result =
       std::snprintf(buffer.data(), buffer.size(), CustomType::kToString);
   if (result < 0) {
@@ -106,13 +106,13 @@
 }
 
 TEST(ToString, Integer_EmptyBuffer_WritesNothing) {
-  auto result = ToString(-1234, span(buffer, 0));
+  auto result = ToString(-1234, std::span(buffer, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
 }
 
 TEST(ToString, Integer_BufferTooSmall_WritesNullTerminator) {
-  auto result = ToString(-1234, span(buffer, 5));
+  auto result = ToString(-1234, std::span(buffer, 5));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer);
@@ -241,7 +241,7 @@
 
 TEST(ToString, StringView_TooSmall_Truncates) {
   std::string_view view = "kale!";
-  EXPECT_EQ(3u, ToString(view, span(buffer, 4)).size());
+  EXPECT_EQ(3u, ToString(view, std::span(buffer, 4)).size());
   EXPECT_STREQ("kal", buffer);
 }
 
@@ -250,8 +250,9 @@
   char test_buffer[sizeof(kOriginal)];
   std::memcpy(test_buffer, kOriginal, sizeof(kOriginal));
 
-  EXPECT_EQ(0u,
-            ToString(std::string_view("Hello!"), span(test_buffer, 0)).size());
+  EXPECT_EQ(
+      0u,
+      ToString(std::string_view("Hello!"), std::span(test_buffer, 0)).size());
   ASSERT_EQ(0, std::memcmp(kOriginal, test_buffer, sizeof(kOriginal)));
 }
 
diff --git a/pw_string/type_to_string.cc b/pw_string/type_to_string.cc
index fd2d1f6..e5ebcf8 100644
--- a/pw_string/type_to_string.cc
+++ b/pw_string/type_to_string.cc
@@ -47,7 +47,7 @@
     10000000000000000000ull,  // 10^19
 };
 
-StatusWithSize HandleExhaustedBuffer(const span<char>& buffer) {
+StatusWithSize HandleExhaustedBuffer(std::span<char> buffer) {
   if (!buffer.empty()) {
     buffer[0] = '\0';
   }
@@ -72,7 +72,7 @@
 // DecimalDigitCount and its table). I didn't measure performance, but I don't
 // think std::to_chars will be faster, so I kept this implementation for now.
 template <>
-StatusWithSize IntToString(uint64_t value, const span<char>& buffer) {
+StatusWithSize IntToString(uint64_t value, std::span<char> buffer) {
   constexpr uint32_t base = 10;
   constexpr uint32_t max_uint32_base_power = 1'000'000'000;
   constexpr uint_fast8_t max_uint32_base_power_exponent = 9;
@@ -110,7 +110,7 @@
   return StatusWithSize(total_digits);
 }
 
-StatusWithSize IntToHexString(uint64_t value, const span<char>& buffer) {
+StatusWithSize IntToHexString(uint64_t value, std::span<char> buffer) {
   const uint_fast8_t digits = HexDigitCount(value);
 
   if (digits >= buffer.size()) {
@@ -127,7 +127,7 @@
 }
 
 template <>
-StatusWithSize IntToString(int64_t value, const span<char>& buffer) {
+StatusWithSize IntToString(int64_t value, std::span<char> buffer) {
   if (value >= 0) {
     return IntToString<uint64_t>(value, buffer);
   }
@@ -146,7 +146,7 @@
 
 // TODO(hepler): Look into using the float overload of std::to_chars when it is
 //     available.
-StatusWithSize FloatAsIntToString(float value, const span<char>& buffer) {
+StatusWithSize FloatAsIntToString(float value, std::span<char> buffer) {
   // If it's finite and fits in an int64_t, print it as a rounded integer.
   if (std::isfinite(value) &&
       std::abs(value) <
@@ -167,11 +167,11 @@
   return HandleExhaustedBuffer(buffer);
 }
 
-StatusWithSize BoolToString(bool value, const span<char>& buffer) {
+StatusWithSize BoolToString(bool value, std::span<char> buffer) {
   return CopyEntireString(value ? "true" : "false", buffer);
 }
 
-StatusWithSize PointerToString(const void* pointer, const span<char>& buffer) {
+StatusWithSize PointerToString(const void* pointer, std::span<char> buffer) {
   if (pointer == nullptr) {
     return CopyEntireString(kNullPointerString, buffer);
   }
@@ -179,7 +179,7 @@
 }
 
 StatusWithSize CopyString(const std::string_view& value,
-                          const span<char>& buffer) {
+                          std::span<char> buffer) {
   if (buffer.empty()) {
     return StatusWithSize::RESOURCE_EXHAUSTED;
   }
@@ -192,7 +192,7 @@
 }
 
 StatusWithSize CopyEntireString(const std::string_view& value,
-                                const span<char>& buffer) {
+                                std::span<char> buffer) {
   if (value.size() >= buffer.size()) {
     return HandleExhaustedBuffer(buffer);
   }
diff --git a/pw_string/type_to_string_test.cc b/pw_string/type_to_string_test.cc
index 482b841..1169222 100644
--- a/pw_string/type_to_string_test.cc
+++ b/pw_string/type_to_string_test.cc
@@ -107,47 +107,47 @@
 class IntToStringTest : public TestWithBuffer {};
 
 TEST_F(IntToStringTest, Unsigned_EmptyBuffer_WritesNothing) {
-  auto result = IntToString(9u, span(buffer_, 0));
+  auto result = IntToString(9u, std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
 TEST_F(IntToStringTest, Unsigned_TooSmall_1Char_OnlyNullTerminates) {
-  auto result = IntToString(9u, span(buffer_, 1));
+  auto result = IntToString(9u, std::span(buffer_, 1));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(IntToStringTest, Unsigned_TooSmall_2Chars_OnlyNullTerminates) {
-  auto result = IntToString(10u, span(buffer_, 2));
+  auto result = IntToString(10u, std::span(buffer_, 2));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(IntToStringTest, Unsigned_TooSmall_3Chars_OnlyNullTerminates) {
-  auto result = IntToString(123u, span(buffer_, 3));
+  auto result = IntToString(123u, std::span(buffer_, 3));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(IntToStringTest, Unsigned_1Char_FitsExactly) {
-  auto result = IntToString(0u, span(buffer_, 2));
+  auto result = IntToString(0u, std::span(buffer_, 2));
   EXPECT_EQ(1u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("0", buffer_);
 
-  result = IntToString(9u, span(buffer_, 2));
+  result = IntToString(9u, std::span(buffer_, 2));
   EXPECT_EQ(1u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("9", buffer_);
 }
 
 TEST_F(IntToStringTest, Unsigned_2Chars_FitsExactly) {
-  auto result = IntToString(10u, span(buffer_, 3));
+  auto result = IntToString(10u, std::span(buffer_, 3));
   EXPECT_EQ(2u, result.size());
   EXPECT_STREQ("10", buffer_);
 }
@@ -155,44 +155,44 @@
 TEST_F(IntToStringTest, Unsigned_MaxFitsExactly) {
   EXPECT_EQ(20u,
             IntToString(std::numeric_limits<uint64_t>::max(),
-                        span(buffer_, sizeof(kUint64Max)))
+                        std::span(buffer_, sizeof(kUint64Max)))
                 .size());
   EXPECT_STREQ(kUint64Max, buffer_);
 }
 
 TEST_F(IntToStringTest, SignedPositive_EmptyBuffer_WritesNothing) {
-  auto result = IntToString(9, span(buffer_, 0));
+  auto result = IntToString(9, std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
 TEST_F(IntToStringTest, SignedPositive_TooSmall_NullTerminates) {
-  auto result = IntToString(9, span(buffer_, 1));
+  auto result = IntToString(9, std::span(buffer_, 1));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(IntToStringTest, SignedPositive_TooSmall_DoesNotWritePastEnd) {
-  EXPECT_EQ(0u, IntToString(9, span(buffer_, 1)).size());
+  EXPECT_EQ(0u, IntToString(9, std::span(buffer_, 1)).size());
   EXPECT_EQ(0, std::memcmp("\0@#$%^&*()!@#$%^&*()", buffer_, sizeof(buffer_)));
 }
 
 TEST_F(IntToStringTest, SignedPositive_1Char_FitsExactly) {
-  auto result = IntToString(0, span(buffer_, 2));
+  auto result = IntToString(0, std::span(buffer_, 2));
   EXPECT_EQ(1u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("0", buffer_);
 
-  result = IntToString(9, span(buffer_, 2));
+  result = IntToString(9, std::span(buffer_, 2));
   EXPECT_EQ(1u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("9", buffer_);
 }
 
 TEST_F(IntToStringTest, SignedPositive_2Chars_FitsExactly) {
-  auto result = IntToString(10, span(buffer_, 4));
+  auto result = IntToString(10, std::span(buffer_, 4));
   EXPECT_EQ(2u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("10", buffer_);
@@ -200,20 +200,20 @@
 
 TEST_F(IntToStringTest, SignedPositive_MaxFitsExactly) {
   auto result = IntToString(std::numeric_limits<int64_t>::max(),
-                            span(buffer_, sizeof(kInt64Min)));
+                            std::span(buffer_, sizeof(kInt64Min)));
   EXPECT_EQ(19u, result.size());
   EXPECT_STREQ(kInt64Max, buffer_);
 }
 
 TEST_F(IntToStringTest, SignedNegative_EmptyBuffer_WritesNothing) {
-  auto result = IntToString(-9, span(buffer_, 0));
+  auto result = IntToString(-9, std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
 TEST_F(IntToStringTest, SignedNegative_TooSmall_NullTerminates) {
-  auto result = IntToString(-9, span(buffer_, 1));
+  auto result = IntToString(-9, std::span(buffer_, 1));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
@@ -221,25 +221,25 @@
 
 TEST_F(IntToStringTest, SignedNegative_TooSmall_DoesNotWritePastEnd) {
   // Note that two \0 are written due to the unsigned IntToString call.
-  EXPECT_EQ(0u, IntToString(-9, span(buffer_, 2)).size());
+  EXPECT_EQ(0u, IntToString(-9, std::span(buffer_, 2)).size());
   EXPECT_EQ(0, std::memcmp("\0\0#$%^&*()!@#$%^&*()", buffer_, sizeof(buffer_)));
 }
 
 TEST_F(IntToStringTest, SignedNegative_FitsExactly) {
-  auto result = IntToString(-9, span(buffer_, 3));
+  auto result = IntToString(-9, std::span(buffer_, 3));
   EXPECT_EQ(2u, result.size());
   EXPECT_STREQ("-9", buffer_);
-  result = IntToString(-99, span(buffer_, 4));
+  result = IntToString(-99, std::span(buffer_, 4));
   EXPECT_EQ(3u, result.size());
   EXPECT_STREQ("-99", buffer_);
-  result = IntToString(-123, span(buffer_, 5));
+  result = IntToString(-123, std::span(buffer_, 5));
   EXPECT_EQ(4u, result.size());
   EXPECT_STREQ("-123", buffer_);
 }
 
 TEST_F(IntToStringTest, SignedNegative_MinFitsExactly) {
   auto result = IntToString(std::numeric_limits<int64_t>::min(),
-                            span(buffer_, sizeof(kInt64Min)));
+                            std::span(buffer_, sizeof(kInt64Min)));
   EXPECT_EQ(20u, result.size());
   EXPECT_STREQ(kInt64Min, buffer_);
 }
@@ -304,14 +304,14 @@
 }
 
 TEST_F(IntToHexStringTest, EmptyBuffer_WritesNothing) {
-  auto result = IntToHexString(0xbeef, span(buffer_, 0));
+  auto result = IntToHexString(0xbeef, std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
 TEST_F(IntToHexStringTest, TooSmall_Truncates) {
-  auto result = IntToHexString(0xbeef, span(buffer_, 3));
+  auto result = IntToHexString(0xbeef, std::span(buffer_, 3));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
@@ -381,21 +381,21 @@
 }
 
 TEST_F(FloatAsIntToStringTest, TooSmall_Numeric_NullTerminates) {
-  auto result = FloatAsIntToString(-3.14e20f, span(buffer_, 1));
+  auto result = FloatAsIntToString(-3.14e20f, std::span(buffer_, 1));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(FloatAsIntToStringTest, TooSmall_Infinity_NullTerminates) {
-  auto result = FloatAsIntToString(-INFINITY, span(buffer_, 3));
+  auto result = FloatAsIntToString(-INFINITY, std::span(buffer_, 3));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(FloatAsIntToStringTest, TooSmall_NaN_NullTerminates) {
-  auto result = FloatAsIntToString(NAN, span(buffer_, 2));
+  auto result = FloatAsIntToString(NAN, std::span(buffer_, 2));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
@@ -411,28 +411,28 @@
 }
 
 TEST_F(CopyStringTest, EmptyBuffer_WritesNothing) {
-  auto result = CopyString("Hello", span(buffer_, 0));
+  auto result = CopyString("Hello", std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
 TEST_F(CopyStringTest, TooSmall_Truncates) {
-  auto result = CopyString("Hi!", span(buffer_, 3));
+  auto result = CopyString("Hi!", std::span(buffer_, 3));
   EXPECT_EQ(2u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("Hi", buffer_);
 }
 
 TEST_F(CopyStringTest, ExactFit) {
-  auto result = CopyString("Hi!", span(buffer_, 4));
+  auto result = CopyString("Hi!", std::span(buffer_, 4));
   EXPECT_EQ(3u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("Hi!", buffer_);
 }
 
 TEST_F(CopyStringTest, NullTerminatorsInString) {
-  ASSERT_EQ(4u, CopyString("\0!\0\0"sv, span(buffer_, 5)).size());
+  ASSERT_EQ(4u, CopyString("\0!\0\0"sv, std::span(buffer_, 5)).size());
   EXPECT_EQ("\0!\0\0"sv, std::string_view(buffer_, 4));
 }
 
@@ -444,35 +444,35 @@
 }
 
 TEST_F(CopyEntireStringTest, EmptyBuffer_WritesNothing) {
-  auto result = CopyEntireString("Hello", span(buffer_, 0));
+  auto result = CopyEntireString("Hello", std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
 TEST_F(CopyEntireStringTest, TooSmall_WritesNothing) {
-  auto result = CopyEntireString("Hi!", span(buffer_, 3));
+  auto result = CopyEntireString("Hi!", std::span(buffer_, 3));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(CopyEntireStringTest, ExactFit) {
-  auto result = CopyEntireString("Hi!", span(buffer_, 4));
+  auto result = CopyEntireString("Hi!", std::span(buffer_, 4));
   EXPECT_EQ(3u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("Hi!", buffer_);
 }
 
 TEST_F(CopyEntireStringTest, NullTerminatorsInString) {
-  ASSERT_EQ(4u, CopyEntireString("\0!\0\0"sv, span(buffer_, 5)).size());
+  ASSERT_EQ(4u, CopyEntireString("\0!\0\0"sv, std::span(buffer_, 5)).size());
   EXPECT_EQ("\0!\0\0"sv, std::string_view(buffer_, 4));
 }
 
 class PointerToStringTest : public TestWithBuffer {};
 
 TEST_F(PointerToStringTest, Nullptr_WritesNull) {
-  EXPECT_EQ(6u, PointerToString(nullptr, span(buffer_, 7)).size());
+  EXPECT_EQ(6u, PointerToString(nullptr, std::span(buffer_, 7)).size());
   EXPECT_STREQ("(null)", buffer_);
 }
 
@@ -485,32 +485,32 @@
 class BoolToStringTest : public TestWithBuffer {};
 
 TEST_F(BoolToStringTest, ExactFit) {
-  EXPECT_EQ(4u, BoolToString(true, span(buffer_, 5)).size());
+  EXPECT_EQ(4u, BoolToString(true, std::span(buffer_, 5)).size());
   EXPECT_STREQ("true", buffer_);
 
-  EXPECT_EQ(5u, BoolToString(false, span(buffer_, 6)).size());
+  EXPECT_EQ(5u, BoolToString(false, std::span(buffer_, 6)).size());
   EXPECT_STREQ("false", buffer_);
 }
 
 TEST_F(BoolToStringTest, True_TooSmall_WritesNullTerminator) {
-  auto result = BoolToString(true, span(buffer_, 4));
+  auto result = BoolToString(true, std::span(buffer_, 4));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(BoolToStringTest, False_TooSmall_WritesNullTerminator) {
-  auto result = BoolToString(false, span(buffer_, 5));
+  auto result = BoolToString(false, std::span(buffer_, 5));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
 TEST_F(BoolToStringTest, EmptyBuffer_WritesNothing) {
-  EXPECT_EQ(0u, BoolToString(true, span(buffer_, 0)).size());
+  EXPECT_EQ(0u, BoolToString(true, std::span(buffer_, 0)).size());
   EXPECT_STREQ(kStartingString, buffer_);
 
-  EXPECT_EQ(0u, BoolToString(false, span(buffer_, 0)).size());
+  EXPECT_EQ(0u, BoolToString(false, std::span(buffer_, 0)).size());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
diff --git a/pw_unit_test/public/pw_unit_test/framework.h b/pw_unit_test/public/pw_unit_test/framework.h
index 7462f1a..cee6a70 100644
--- a/pw_unit_test/public/pw_unit_test/framework.h
+++ b/pw_unit_test/public/pw_unit_test/framework.h
@@ -21,6 +21,7 @@
 #include <cstdint>
 #include <cstring>
 #include <new>
+#include <span>
 
 #include "pw_polyfill/standard.h"
 #include "pw_preprocessor/concat.h"
@@ -115,11 +116,13 @@
 // ASSERT statements in tests.
 //
 // You can add support for displaying custom types by defining a ToString
-// overload. For example:
+// template specialization. For example:
 //
 //   namespace pw {
 //
-//   StatusWithSize ToString(const MyType& value, const span<char>& buffer) {
+//   template <>
+//   StatusWithSize ToString<MyType>(const MyType& value,
+//                                   std::span<char> buffer) {
 //     return string::Format("<MyType|%d>", value.id);
 //   }
 //
@@ -127,7 +130,7 @@
 //
 // See the documentation in pw_string/string_builder.h for more information.
 template <typename T>
-StatusWithSize UnknownTypeToString(const T& value, const span<char>& buffer) {
+StatusWithSize UnknownTypeToString(const T& value, std::span<char> buffer) {
   StringBuilder sb(buffer);
   sb << '<' << sizeof(value) << "-byte object at 0x" << &value << '>';
   return sb.status_with_size();