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;