pw_string: pw::InlineString implicit conversions from std::string_view

std::string requires conversions from std::string_view to be explicit.
For pw::InlineString, explicit conversions are more cumbersome because
the capacity template parameter must be specified. This makes it more
awkward use std::string_view with pw::InlineString<> when calling a
function or initializing a struct member with aggregate initialization.

This change allows implicit conversions from std::string_view to
pw::InlineString<kCapacity>. Types that convert to std::string_view
still require explicit conversion.

Change-Id: I3cc8264753a0ba271364a900522ba8f77747b079
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/110394
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/pw_string/docs.rst b/pw_string/docs.rst
index d731939..f7d1206 100644
--- a/pw_string/docs.rst
+++ b/pw_string/docs.rst
@@ -80,15 +80,15 @@
 .. code-block:: c++
 
   // Initialize from a C string.
-  pw::InlineString<32> my_string = "Literally";
-  my_string.append('?', 3);   // contains "Literally???"
+  pw::InlineString<32> inline_string = "Literally";
+  inline_string.append('?', 3);   // contains "Literally???"
 
   // Supports copying into known-capacity strings.
-  pw::InlineString<64> other = my_string;
+  pw::InlineString<64> other = inline_string;
 
   // Supports various helpful std::string functions
-  if (my_string.starts_with("Lit") || my_string == "not\0literally"sv) {
-    other += my_string;
+  if (inline_string.starts_with("Lit") || inline_string == "not\0literally"sv) {
+    other += inline_string;
   }
 
   // Like std::string, InlineString is always null terminated when accessed
@@ -96,11 +96,17 @@
   // length-delimited strings for APIs that expect null-terminated strings.
   std::string_view file(".gif");
   if (std::fopen(pw::InlineString<kMaxNameLen>(file).c_str(), "r") == nullptr) {
-    // Integrates with std::string_view.
-    my_string = std::string_view("not\0literally", 12);
-    TakesAStringView(std::string_view(my_string));
+    return;
   }
 
+  // pw::InlineString integrates well with std::string_view. It supports
+  // implicit conversions to and from std::string_view.
+  inline_string = std::string_view("not\0literally", 12);
+
+  FunctionThatTakesAStringView(inline_string);
+
+  FunctionThatTakesAnInlineString(std::string_view("1234", 4));
+
 All :cpp:type:`pw::InlineString` operations may be performed on strings without
 specifying their capacity.
 
@@ -145,7 +151,7 @@
 
    // Deduces a capacity of 6 characters to match the 6-character string literal
    // (counting the null terminator).
-   pw::InlineBasicString my_string = "12345";
+   pw::InlineBasicString inline_string = "12345";
 
    // In C++20, CTAD may be used with the pw::InlineString alias.
    pw::InlineString my_other_string("123456789");
diff --git a/pw_string/public/pw_string/internal/string_common_functions.inc b/pw_string/public/pw_string/internal/string_common_functions.inc
index 365f052..daebfd0 100644
--- a/pw_string/public/pw_string/internal/string_common_functions.inc
+++ b/pw_string/public/pw_string/internal/string_common_functions.inc
@@ -21,7 +21,7 @@
 // class because:
 //
 //   1. Many functions return a *this reference. The functions should to return
-//      a reference to the exact type they called on rather than the
+//      a reference to the exact type they are called on rather than the
 //      generic-capacity base class.
 //   2. Operations on the generic base class cannot be constexpr unless the
 //      class is an InlineBasicString<T, 0>. The functions must be defined in
diff --git a/pw_string/public/pw_string/internal/string_impl.h b/pw_string/public/pw_string/internal/string_impl.h
index d0fcce7..b90bd9e 100644
--- a/pw_string/public/pw_string/internal/string_impl.h
+++ b/pw_string/public/pw_string/internal/string_impl.h
@@ -45,6 +45,12 @@
 using EnableIfStringViewLike = std::enable_if_t<
     std::is_convertible<const T&, std::basic_string_view<CharType>>() &&
     !std::is_convertible<const T&, const CharType*>()>;
+
+template <typename CharType, typename T>
+using EnableIfStringViewLikeButNotStringView = std::enable_if_t<
+    !std::is_same<T, std::basic_string_view<CharType>>() &&
+    std::is_convertible<const T&, std::basic_string_view<CharType>>() &&
+    !std::is_convertible<const T&, const CharType*>()>;
 #endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
 
 // Reserved capacity that is used to represent a generic-length
diff --git a/pw_string/public/pw_string/string.h b/pw_string/public/pw_string/string.h
index b4ddead..e17c7b5 100644
--- a/pw_string/public/pw_string/string.h
+++ b/pw_string/public/pw_string/string.h
@@ -132,13 +132,31 @@
       : InlineBasicString(list.begin(), list.size()) {}
 
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)  // std::string_view is a C++17 feature
+  // Unlike std::string, pw::InlineString<> supports implicit conversions from
+  // std::string_view. However, explicit conversions are still required from
+  // types that convert to std::string_view, as with std::string.
+  //
+  // pw::InlineString<> allows implicit conversions from std::string_view
+  // because it can be cumbersome to specify the capacity parameter. In
+  // particular, this can make using aggregate initialization more difficult.
+  //
+  // This explicit constructor is enabled for an argument that converts to
+  // std::string_view, but is not a std::string_view.
+  template <
+      typename StringViewLike,
+      string_impl::EnableIfStringViewLikeButNotStringView<T, StringViewLike>* =
+          nullptr>
+  explicit constexpr InlineBasicString(const StringViewLike& string)
+      : InlineBasicString(std::basic_string_view<T>(string)) {}
+
+  // This converting constructor is enabled for std::string_view, but not types
+  // that convert to it.
   template <typename StringView,
-            typename = string_impl::EnableIfStringViewLike<T, StringView>>
-  explicit constexpr InlineBasicString(const StringView& string)
-      : InlineBasicString() {
-    const std::basic_string_view<T> view = string;
-    assign(view.data(), view.size());
-  }
+            std::enable_if_t<
+                std::is_same<StringView, std::basic_string_view<T>>::value>* =
+                nullptr>
+  constexpr InlineBasicString(const StringView& view)
+      : InlineBasicString(view.data(), view.size()) {}
 
   template <typename StringView,
             typename = string_impl::EnableIfStringViewLike<T, StringView>>
diff --git a/pw_string/string_test.cc b/pw_string/string_test.cc
index 7c8a3b8..d1890db 100644
--- a/pw_string/string_test.cc
+++ b/pw_string/string_test.cc
@@ -495,6 +495,15 @@
 }
 
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)
+
+constexpr InlineString<16> TakesInlineString(const InlineString<16>& str) {
+  return str;
+}
+
+struct HoldsString {
+  InlineString<16> value;
+};
+
 TEST(InlineString, Construct_StringView) {
   TEST_STRING(InlineString<0>(""sv), , "");
   TEST_STRING(InlineString<10>(""sv), , "");
@@ -505,6 +514,19 @@
   TEST_STRING(InlineString<10>(StringViewLike<char>("01234", 5)), , "01234");
   TEST_STRING(InlineString<10>(kStringViewLike10), , "0123456789");
 
+  // pw::InlineString supports implicit conversion from std::string_view.
+  constexpr InlineString<16> implicit_call = TakesInlineString("1234"sv);
+  EXPECT_CONSTEXPR_PW_STRING(implicit_call, "1234");
+
+  constexpr HoldsString implicit_initialize_1{.value = "1234"sv};
+  EXPECT_CONSTEXPR_PW_STRING(implicit_initialize_1.value, "1234");
+
+  constexpr HoldsString implicit_initialize_2{.value{"1234"sv}};
+  EXPECT_CONSTEXPR_PW_STRING(implicit_initialize_2.value, "1234");
+
+  constexpr HoldsString implicit_initialize_3{"1234"sv};
+  EXPECT_CONSTEXPR_PW_STRING(implicit_initialize_3.value, "1234");
+
 #if PW_NC_TEST(Construct_StringView_DoesNotFit)
   PW_NC_EXPECT(
       "pw::InlineString must be at least as large as the source string");
@@ -513,11 +535,25 @@
   PW_NC_EXPECT(
       "pw::InlineString must be at least as large as the source string");
   constexpr [[maybe_unused]] InlineString<5> bad_string(kEmptyCapacity10);
-#elif PW_NC_TEST(Construct_StringView_NoImplicitConversionFromAmbiguousClass)
+#elif PW_NC_TEST(Construct_StringView_NoConversionFromAmbiguousClass)
   PW_NC_EXPECT_CLANG("no matching constructor");
   PW_NC_EXPECT_GCC("no matching function for call to");
   [[maybe_unused]] InlineString<10> fail(
       StringViewLikeButConvertsToPointer<char>("1", 1));
+#elif PW_NC_TEST(Construct_StringView_NoImplicitConversionFromStringViewLike)
+  PW_NC_EXPECT_CLANG("no matching function for call to 'TakesInlineString'");
+  PW_NC_EXPECT_GCC(
+      "invalid initialization of reference of type .* from expression of type "
+      "'const pw::StringViewLike<char>'");
+  TakesInlineString(kStringViewLike10);
+#elif PW_NC_TEST(Construct_StringView_NoImplicitConvFromStringViewLikeInInit1)
+  PW_NC_EXPECT_GCC("could not convert 'pw::kStringViewLike10'");
+  PW_NC_EXPECT_CLANG("no viable conversion from 'const StringViewLike<char>'");
+  (void)HoldsString{.value = kStringViewLike10};
+#elif PW_NC_TEST(Construct_StringView_NoImplicitConvFromStringViewLikeInInit2)
+  PW_NC_EXPECT_GCC("could not convert 'pw::kStringViewLike10'");
+  PW_NC_EXPECT_CLANG("no viable conversion from 'const StringViewLike<char>'");
+  (void)HoldsString{kStringViewLike10};
 #endif  // PW_NC_TEST
 }
 
@@ -994,7 +1030,7 @@
     str.assign(kEmptyCapacity10);
     return str;
   }();
-#elif PW_NC_TEST(Assign_StringView_NoImplicitConversionFromAmbiguousClass)
+#elif PW_NC_TEST(Assign_StringView_NoAssignmentFromAmbiguousClass)
   PW_NC_EXPECT_CLANG("no matching member function for call to");
   PW_NC_EXPECT_GCC("no matching function for call to");
   [[maybe_unused]] InlineString<10> fail;