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/stl_type_traits.h"
"strings/internal/string_constant.h"
"strings/internal/stringify_sink.h"
"strings/match.cc"
"strings/match.h"
"strings/numbers.cc"

@ -42,6 +42,7 @@ cc_library(
"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",

@ -41,6 +41,7 @@ absl_cc_library(
"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"

@ -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
// 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
//------------------------------------------------------------------------------
//
// AbslStringify()
// 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:
//
// 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 @@ struct AlphaNumBuffer {
// int x;
// int y;
// };
// -----------------------------------------------------------------------------
class StringifySink {
public:
void Append(size_t count, char ch);
void Append(string_view v);
#ifndef ABSL_STRINGS_STR_CAT_H_
#define ABSL_STRINGS_STR_CAT_H_
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...)`.
friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) {
sink->Append(v);
}
#include "absl/base/port.h"
#include "absl/strings/internal/stringify_sink.h"
#include "absl/strings/numbers.h"
#include "absl/strings/string_view.h"
template <typename T>
friend string_view ExtractStringification(StringifySink& sink, const T& v);
namespace absl {
ABSL_NAMESPACE_BEGIN
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 @@ struct Dec {
// `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 @@ class AlphaNum {
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)

@ -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 @@ class Arg {
// 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 @@ class Arg {
// 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 @@ class Arg {
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 @@ class Arg {
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 @@ class Arg {
// `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.

@ -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 @@ TEST(SubstituteTest, Substitute) {
// 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 @@ TEST(SubstituteTest, Substitute) {
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 @@ TEST(SubstituteTest, SubstituteAndAppend) {
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) {

Loading…
Cancel
Save