Support stringification of user-defined types in AbslStringify in absl::Substitute.
We are also moving some internals into an internal header.
`HasAbslStringify` was not previously in an internal namespace but was intended
to be and has now been moved to an internal namespace. This is in adherence to
our compatibility guidelines which wave requirements for APIs within their
first 30 days of public release (See https://abseil.io/about/compatibility for
details).
PiperOrigin-RevId: 481190705
Change-Id: I4c0c348f269ea8d76ea3d4bd5a2c41cce475dc04
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake
index d8ddcb3..f18b54a 100644
--- a/CMake/AbseilDll.cmake
+++ b/CMake/AbseilDll.cmake
@@ -240,6 +240,7 @@
"strings/internal/cordz_update_tracker.h"
"strings/internal/stl_type_traits.h"
"strings/internal/string_constant.h"
+ "strings/internal/stringify_sink.h"
"strings/match.cc"
"strings/match.h"
"strings/numbers.cc"
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel
index 5b12c01..c698981 100644
--- a/absl/strings/BUILD.bazel
+++ b/absl/strings/BUILD.bazel
@@ -42,6 +42,7 @@
"internal/stl_type_traits.h",
"internal/str_join_internal.h",
"internal/str_split_internal.h",
+ "internal/stringify_sink.h",
"match.cc",
"numbers.cc",
"str_cat.cc",
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt
index 01f8618..fe82e1d 100644
--- a/absl/strings/CMakeLists.txt
+++ b/absl/strings/CMakeLists.txt
@@ -41,6 +41,7 @@
"internal/charconv_parse.h"
"internal/memutil.cc"
"internal/memutil.h"
+ "internal/stringify_sink.h"
"internal/stl_type_traits.h"
"internal/str_join_internal.h"
"internal/str_split_internal.h"
diff --git a/absl/strings/internal/stringify_sink.h b/absl/strings/internal/stringify_sink.h
new file mode 100644
index 0000000..a83f70e
--- /dev/null
+++ b/absl/strings/internal/stringify_sink.h
@@ -0,0 +1,68 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed 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
+//
+// https://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 ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
+#define ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
+
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace strings_internal {
+class StringifySink {
+ public:
+ void Append(size_t count, char ch);
+
+ void Append(string_view v);
+
+ bool PutPaddedString(string_view v, int width, int precision, bool left);
+
+ // Support `absl::Format(&sink, format, args...)`.
+ friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) {
+ sink->Append(v);
+ }
+
+ private:
+ template <typename T>
+ friend string_view ExtractStringification(StringifySink& sink, const T& v);
+
+ std::string buffer_;
+};
+
+template <typename T>
+string_view ExtractStringification(StringifySink& sink, const T& v) {
+ AbslStringify(sink, v);
+ return sink.buffer_;
+}
+
+template <typename T, typename = void>
+struct HasAbslStringify : std::false_type {};
+
+template <typename T>
+struct HasAbslStringify<T, std::enable_if_t<std::is_void<decltype(AbslStringify(
+ std::declval<strings_internal::StringifySink&>(),
+ std::declval<const T&>()))>::value>>
+ : std::true_type {};
+
+} // namespace strings_internal
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h
index 8a63be0..1a37faa 100644
--- a/absl/strings/str_cat.h
+++ b/absl/strings/str_cat.h
@@ -48,53 +48,22 @@
// `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using
// a `PadSpec` enum.
//
-// -----------------------------------------------------------------------------
-
-#ifndef ABSL_STRINGS_STR_CAT_H_
-#define ABSL_STRINGS_STR_CAT_H_
-
-#include <array>
-#include <cstdint>
-#include <string>
-#include <type_traits>
-#include <utility>
-#include <vector>
-
-#include "absl/base/port.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/string_view.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-
-namespace strings_internal {
-// AlphaNumBuffer allows a way to pass a string to StrCat without having to do
-// memory allocation. It is simply a pair of a fixed-size character array, and
-// a size. Please don't use outside of absl, yet.
-template <size_t max_size>
-struct AlphaNumBuffer {
- std::array<char, max_size> data;
- size_t size;
-};
-
-//------------------------------------------------------------------------------
-// StrCat Extension
-//------------------------------------------------------------------------------
+// User-defined types can be formatted with the `AbslStringify()` customization
+// point. The API relies on detecting an overload in the user-defined type's
+// namespace of a free (non-member) `AbslStringify()` function as a definition
+// (typically declared as a friend and implemented in-line.
+// with the following signature:
//
-// AbslStringify()
-//
-// A simple customization API for formatting user-defined types using
-// absl::StrCat(). The API relies on detecting an overload in the
-// user-defined type's namespace of a free (non-member) `AbslStringify()`
-// function as a friend definition with the following signature:
+// class MyClass { ... };
//
// template <typename Sink>
-// void AbslStringify(Sink& sink, const X& value);
+// void AbslStringify(Sink& sink, const MyClass& value);
//
// An `AbslStringify()` overload for a type should only be declared in the same
// file and namespace as said type.
//
-// Note that AbslStringify() also supports use with absl::StrFormat().
+// Note that `AbslStringify()` also supports use with `absl::StrFormat()` and
+// `absl::Substitute()`.
//
// Example:
//
@@ -113,33 +82,36 @@
// int x;
// int y;
// };
+// -----------------------------------------------------------------------------
-class StringifySink {
- public:
- void Append(size_t count, char ch);
+#ifndef ABSL_STRINGS_STR_CAT_H_
+#define ABSL_STRINGS_STR_CAT_H_
- void Append(string_view v);
+#include <array>
+#include <cstdint>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
- bool PutPaddedString(string_view v, int width, int precision, bool left);
+#include "absl/base/port.h"
+#include "absl/strings/internal/stringify_sink.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
- // Support `absl::Format(&sink, format, args...)`.
- friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) {
- sink->Append(v);
- }
+namespace absl {
+ABSL_NAMESPACE_BEGIN
- template <typename T>
- friend string_view ExtractStringification(StringifySink& sink, const T& v);
-
- private:
- std::string buffer_;
+namespace strings_internal {
+// AlphaNumBuffer allows a way to pass a string to StrCat without having to do
+// memory allocation. It is simply a pair of a fixed-size character array, and
+// a size. Please don't use outside of absl, yet.
+template <size_t max_size>
+struct AlphaNumBuffer {
+ std::array<char, max_size> data;
+ size_t size;
};
-template <typename T>
-string_view ExtractStringification(StringifySink& sink, const T& v) {
- AbslStringify(sink, v);
- return sink.buffer_;
-}
-
} // namespace strings_internal
// Enum that specifies the number of significant digits to return in a `Hex` or
@@ -272,15 +244,6 @@
// `StrAppend()`, providing efficient conversion of numeric, boolean, and
// hexadecimal values (through the `Hex` type) into strings.
-template <typename T, typename = void>
-struct HasAbslStringify : std::false_type {};
-
-template <typename T>
-struct HasAbslStringify<T, std::enable_if_t<std::is_void<decltype(AbslStringify(
- std::declval<strings_internal::StringifySink&>(),
- std::declval<const T&>()))>::value>>
- : std::true_type {};
-
class AlphaNum {
public:
// No bool ctor -- bools convert to an integral type.
@@ -329,7 +292,7 @@
AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit)
template <typename T, typename = typename std::enable_if<
- HasAbslStringify<T>::value>::type>
+ strings_internal::HasAbslStringify<T>::value>::type>
AlphaNum( // NOLINT(runtime/explicit)
const T& v, // NOLINT(runtime/explicit)
strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit)
diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h
index 692fd03..5c3f6ef 100644
--- a/absl/strings/substitute.h
+++ b/absl/strings/substitute.h
@@ -55,6 +55,8 @@
// * bool (Printed as "true" or "false")
// * pointer types other than char* (Printed as "0x<lower case hex string>",
// except that null is printed as "NULL")
+// * user-defined types via the `AbslStringify()` customization point. See the
+// documentation for `absl::StrCat` for an explanation on how to use this.
//
// If an invalid format string is provided, Substitute returns an empty string
// and SubstituteAndAppend does not change the provided output string.
@@ -79,6 +81,7 @@
#include "absl/base/port.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
+#include "absl/strings/internal/stringify_sink.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
@@ -102,14 +105,14 @@
// Overloads for string-y things
//
// Explicitly overload `const char*` so the compiler doesn't cast to `bool`.
- Arg(const char* value) // NOLINT(runtime/explicit)
+ Arg(const char* value) // NOLINT(google-explicit-constructor)
: piece_(absl::NullSafeStringView(value)) {}
template <typename Allocator>
Arg( // NOLINT
const std::basic_string<char, std::char_traits<char>, Allocator>&
value) noexcept
: piece_(value) {}
- Arg(absl::string_view value) // NOLINT(runtime/explicit)
+ Arg(absl::string_view value) // NOLINT(google-explicit-constructor)
: piece_(value) {}
// Overloads for primitives
@@ -119,7 +122,7 @@
// probably using them as 8-bit integers and would probably prefer an integer
// representation. However, we can't really know, so we make the caller decide
// what to do.
- Arg(char value) // NOLINT(runtime/explicit)
+ Arg(char value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, 1) {
scratch_[0] = value;
}
@@ -133,12 +136,12 @@
static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) -
scratch_)) {}
- Arg(int value) // NOLINT(runtime/explicit)
+ Arg(int value) // NOLINT(google-explicit-constructor)
: piece_(scratch_,
static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) -
scratch_)) {}
- Arg(unsigned int value) // NOLINT(runtime/explicit)
+ Arg(unsigned int value) // NOLINT(google-explicit-constructor)
: piece_(scratch_,
static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) -
@@ -163,17 +166,23 @@
static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) -
scratch_)) {}
- Arg(float value) // NOLINT(runtime/explicit)
+ Arg(float value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) {
}
- Arg(double value) // NOLINT(runtime/explicit)
+ Arg(double value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) {
}
- Arg(bool value) // NOLINT(runtime/explicit)
+ Arg(bool value) // NOLINT(google-explicit-constructor)
: piece_(value ? "true" : "false") {}
- Arg(Hex hex); // NOLINT(runtime/explicit)
- Arg(Dec dec); // NOLINT(runtime/explicit)
+ template <typename T, typename = typename std::enable_if<
+ strings_internal::HasAbslStringify<T>::value>::type>
+ Arg( // NOLINT(google-explicit-constructor)
+ const T& v, strings_internal::StringifySink&& sink = {})
+ : piece_(strings_internal::ExtractStringification(sink, v)) {}
+
+ Arg(Hex hex); // NOLINT(google-explicit-constructor)
+ Arg(Dec dec); // NOLINT(google-explicit-constructor)
// vector<bool>::reference and const_reference require special help to convert
// to `Arg` because it requires two user defined conversions.
@@ -188,7 +197,7 @@
// `void*` values, with the exception of `char*`, are printed as
// "0x<hex value>". However, in the case of `nullptr`, "NULL" is printed.
- Arg(const void* value); // NOLINT(runtime/explicit)
+ Arg(const void* value); // NOLINT(google-explicit-constructor)
// Normal enums are already handled by the integer formatters.
// This overload matches only scoped enums.
diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc
index 9e6b940..9f04545 100644
--- a/absl/strings/substitute_test.cc
+++ b/absl/strings/substitute_test.cc
@@ -22,6 +22,16 @@
namespace {
+struct MyStruct {
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const MyStruct& s) {
+ sink.Append("MyStruct{.value = ");
+ sink.Append(absl::StrCat(s.value));
+ sink.Append("}");
+ }
+ int value;
+};
+
TEST(SubstituteTest, Substitute) {
// Basic.
EXPECT_EQ("Hello, world!", absl::Substitute("$0, $1!", "Hello", "world"));
@@ -70,7 +80,7 @@
// Volatile Pointer.
// Like C++ streamed I/O, such pointers implicitly become bool
volatile int vol = 237;
- volatile int *volatile volptr = &vol;
+ volatile int* volatile volptr = &vol;
str = absl::Substitute("$0", volptr);
EXPECT_EQ("true", str);
@@ -128,6 +138,11 @@
const char* null_cstring = nullptr;
EXPECT_EQ("Text: ''", absl::Substitute("Text: '$0'", null_cstring));
+
+ MyStruct s1 = MyStruct{17};
+ MyStruct s2 = MyStruct{1043};
+ EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}",
+ absl::Substitute("$0, $1", s1, s2));
}
TEST(SubstituteTest, SubstituteAndAppend) {
@@ -171,6 +186,12 @@
absl::SubstituteAndAppend(&str, "$0 $1 $2 $3 $4 $5 $6 $7 $8 $9", "a", "b",
"c", "d", "e", "f", "g", "h", "i", "j");
EXPECT_EQ("a b c d e f g h i j", str);
+
+ str.clear();
+ MyStruct s1 = MyStruct{17};
+ MyStruct s2 = MyStruct{1043};
+ absl::SubstituteAndAppend(&str, "$0, $1", s1, s2);
+ EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}", str);
}
TEST(SubstituteTest, VectorBoolRef) {