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
pull/1298/head
Andy Soffer 2 years ago committed by Copybara-Service
parent f073fe8ee5
commit 5631e52ed7
  1. 1
      CMake/AbseilDll.cmake
  2. 1
      absl/strings/BUILD.bazel
  3. 1
      absl/strings/CMakeLists.txt
  4. 68
      absl/strings/internal/stringify_sink.h
  5. 103
      absl/strings/str_cat.h
  6. 31
      absl/strings/substitute.h
  7. 23
      absl/strings/substitute_test.cc

@ -240,6 +240,7 @@ set(ABSL_INTERNAL_DLL_FILES
"strings/internal/cordz_update_tracker.h" "strings/internal/cordz_update_tracker.h"
"strings/internal/stl_type_traits.h" "strings/internal/stl_type_traits.h"
"strings/internal/string_constant.h" "strings/internal/string_constant.h"
"strings/internal/stringify_sink.h"
"strings/match.cc" "strings/match.cc"
"strings/match.h" "strings/match.h"
"strings/numbers.cc" "strings/numbers.cc"

@ -42,6 +42,7 @@ cc_library(
"internal/stl_type_traits.h", "internal/stl_type_traits.h",
"internal/str_join_internal.h", "internal/str_join_internal.h",
"internal/str_split_internal.h", "internal/str_split_internal.h",
"internal/stringify_sink.h",
"match.cc", "match.cc",
"numbers.cc", "numbers.cc",
"str_cat.cc", "str_cat.cc",

@ -41,6 +41,7 @@ absl_cc_library(
"internal/charconv_parse.h" "internal/charconv_parse.h"
"internal/memutil.cc" "internal/memutil.cc"
"internal/memutil.h" "internal/memutil.h"
"internal/stringify_sink.h"
"internal/stl_type_traits.h" "internal/stl_type_traits.h"
"internal/str_join_internal.h" "internal/str_join_internal.h"
"internal/str_split_internal.h" "internal/str_split_internal.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_

@ -48,53 +48,22 @@
// `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using // `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using
// a `PadSpec` enum. // a `PadSpec` enum.
// //
// ----------------------------------------------------------------------------- // User-defined types can be formatted with the `AbslStringify()` customization
// point. The API relies on detecting an overload in the user-defined type's
#ifndef ABSL_STRINGS_STR_CAT_H_ // namespace of a free (non-member) `AbslStringify()` function as a definition
#define ABSL_STRINGS_STR_CAT_H_ // (typically declared as a friend and implemented in-line.
// with the following signature:
#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
//------------------------------------------------------------------------------
//
// AbslStringify()
// //
// A simple customization API for formatting user-defined types using // class MyClass { ... };
// 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:
// //
// template <typename Sink> // 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 // An `AbslStringify()` overload for a type should only be declared in the same
// file and namespace as said type. // 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: // Example:
// //
@ -113,33 +82,36 @@ struct AlphaNumBuffer {
// int x; // int x;
// int y; // int y;
// }; // };
// -----------------------------------------------------------------------------
class StringifySink { #ifndef ABSL_STRINGS_STR_CAT_H_
public: #define ABSL_STRINGS_STR_CAT_H_
void Append(size_t count, char ch);
void Append(string_view v);
bool PutPaddedString(string_view v, int width, int precision, bool left); #include <array>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
// Support `absl::Format(&sink, format, args...)`. #include "absl/base/port.h"
friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { #include "absl/strings/internal/stringify_sink.h"
sink->Append(v); #include "absl/strings/numbers.h"
} #include "absl/strings/string_view.h"
template <typename T> namespace absl {
friend string_view ExtractStringification(StringifySink& sink, const T& v); ABSL_NAMESPACE_BEGIN
private: namespace strings_internal {
std::string buffer_; // 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 } // namespace strings_internal
// Enum that specifies the number of significant digits to return in a `Hex` or // Enum that specifies the number of significant digits to return in a `Hex` or
@ -272,15 +244,6 @@ struct Dec {
// `StrAppend()`, providing efficient conversion of numeric, boolean, and // `StrAppend()`, providing efficient conversion of numeric, boolean, and
// hexadecimal values (through the `Hex` type) into strings. // 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 { class AlphaNum {
public: public:
// No bool ctor -- bools convert to an integral type. // No bool ctor -- bools convert to an integral type.
@ -329,7 +292,7 @@ class AlphaNum {
AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit) AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit)
template <typename T, typename = typename std::enable_if< template <typename T, typename = typename std::enable_if<
HasAbslStringify<T>::value>::type> strings_internal::HasAbslStringify<T>::value>::type>
AlphaNum( // NOLINT(runtime/explicit) AlphaNum( // NOLINT(runtime/explicit)
const T& v, // NOLINT(runtime/explicit) const T& v, // NOLINT(runtime/explicit)
strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit) strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit)

@ -55,6 +55,8 @@
// * bool (Printed as "true" or "false") // * bool (Printed as "true" or "false")
// * pointer types other than char* (Printed as "0x<lower case hex string>", // * pointer types other than char* (Printed as "0x<lower case hex string>",
// except that null is printed as "NULL") // 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 // If an invalid format string is provided, Substitute returns an empty string
// and SubstituteAndAppend does not change the provided output string. // and SubstituteAndAppend does not change the provided output string.
@ -79,6 +81,7 @@
#include "absl/base/port.h" #include "absl/base/port.h"
#include "absl/strings/ascii.h" #include "absl/strings/ascii.h"
#include "absl/strings/escaping.h" #include "absl/strings/escaping.h"
#include "absl/strings/internal/stringify_sink.h"
#include "absl/strings/numbers.h" #include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h" #include "absl/strings/str_split.h"
@ -102,14 +105,14 @@ class Arg {
// Overloads for string-y things // Overloads for string-y things
// //
// Explicitly overload `const char*` so the compiler doesn't cast to `bool`. // 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)) {} : piece_(absl::NullSafeStringView(value)) {}
template <typename Allocator> template <typename Allocator>
Arg( // NOLINT Arg( // NOLINT
const std::basic_string<char, std::char_traits<char>, Allocator>& const std::basic_string<char, std::char_traits<char>, Allocator>&
value) noexcept value) noexcept
: piece_(value) {} : piece_(value) {}
Arg(absl::string_view value) // NOLINT(runtime/explicit) Arg(absl::string_view value) // NOLINT(google-explicit-constructor)
: piece_(value) {} : piece_(value) {}
// Overloads for primitives // Overloads for primitives
@ -119,7 +122,7 @@ class Arg {
// probably using them as 8-bit integers and would probably prefer an integer // 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 // representation. However, we can't really know, so we make the caller decide
// what to do. // what to do.
Arg(char value) // NOLINT(runtime/explicit) Arg(char value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, 1) { : piece_(scratch_, 1) {
scratch_[0] = value; scratch_[0] = value;
} }
@ -133,12 +136,12 @@ class Arg {
static_cast<size_t>( static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) - numbers_internal::FastIntToBuffer(value, scratch_) -
scratch_)) {} scratch_)) {}
Arg(int value) // NOLINT(runtime/explicit) Arg(int value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, : piece_(scratch_,
static_cast<size_t>( static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) - numbers_internal::FastIntToBuffer(value, scratch_) -
scratch_)) {} scratch_)) {}
Arg(unsigned int value) // NOLINT(runtime/explicit) Arg(unsigned int value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, : piece_(scratch_,
static_cast<size_t>( static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) - numbers_internal::FastIntToBuffer(value, scratch_) -
@ -163,17 +166,23 @@ class Arg {
static_cast<size_t>( static_cast<size_t>(
numbers_internal::FastIntToBuffer(value, scratch_) - numbers_internal::FastIntToBuffer(value, scratch_) -
scratch_)) {} scratch_)) {}
Arg(float value) // NOLINT(runtime/explicit) Arg(float value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) { : 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_)) { : piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) {
} }
Arg(bool value) // NOLINT(runtime/explicit) Arg(bool value) // NOLINT(google-explicit-constructor)
: piece_(value ? "true" : "false") {} : piece_(value ? "true" : "false") {}
Arg(Hex hex); // NOLINT(runtime/explicit) template <typename T, typename = typename std::enable_if<
Arg(Dec dec); // NOLINT(runtime/explicit) 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 // vector<bool>::reference and const_reference require special help to convert
// to `Arg` because it requires two user defined conversions. // to `Arg` because it requires two user defined conversions.
@ -188,7 +197,7 @@ class Arg {
// `void*` values, with the exception of `char*`, are printed as // `void*` values, with the exception of `char*`, are printed as
// "0x<hex value>". However, in the case of `nullptr`, "NULL" is printed. // "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. // Normal enums are already handled by the integer formatters.
// This overload matches only scoped enums. // This overload matches only scoped enums.

@ -22,6 +22,16 @@
namespace { 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) { TEST(SubstituteTest, Substitute) {
// Basic. // Basic.
EXPECT_EQ("Hello, world!", absl::Substitute("$0, $1!", "Hello", "world")); EXPECT_EQ("Hello, world!", absl::Substitute("$0, $1!", "Hello", "world"));
@ -70,7 +80,7 @@ TEST(SubstituteTest, Substitute) {
// Volatile Pointer. // Volatile Pointer.
// Like C++ streamed I/O, such pointers implicitly become bool // Like C++ streamed I/O, such pointers implicitly become bool
volatile int vol = 237; volatile int vol = 237;
volatile int *volatile volptr = &vol; volatile int* volatile volptr = &vol;
str = absl::Substitute("$0", volptr); str = absl::Substitute("$0", volptr);
EXPECT_EQ("true", str); EXPECT_EQ("true", str);
@ -128,6 +138,11 @@ TEST(SubstituteTest, Substitute) {
const char* null_cstring = nullptr; const char* null_cstring = nullptr;
EXPECT_EQ("Text: ''", absl::Substitute("Text: '$0'", null_cstring)); 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) { TEST(SubstituteTest, SubstituteAndAppend) {
@ -171,6 +186,12 @@ TEST(SubstituteTest, SubstituteAndAppend) {
absl::SubstituteAndAppend(&str, "$0 $1 $2 $3 $4 $5 $6 $7 $8 $9", "a", "b", absl::SubstituteAndAppend(&str, "$0 $1 $2 $3 $4 $5 $6 $7 $8 $9", "a", "b",
"c", "d", "e", "f", "g", "h", "i", "j"); "c", "d", "e", "f", "g", "h", "i", "j");
EXPECT_EQ("a b c d e f g h i j", str); 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) { TEST(SubstituteTest, VectorBoolRef) {

Loading…
Cancel
Save