absl/strings: Prepare helper for printing objects to string representations.
PiperOrigin-RevId: 847935770
Change-Id: I7f96940e5ba11d6a602d34e7dc3dbfde112bb142
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake
index b8b137c..cd633a1 100644
--- a/CMake/AbseilDll.cmake
+++ b/CMake/AbseilDll.cmake
@@ -331,6 +331,9 @@
"strings/internal/cordz_update_tracker.h"
"strings/internal/damerau_levenshtein_distance.h"
"strings/internal/damerau_levenshtein_distance.cc"
+ "strings/internal/generic_printer.cc"
+ "strings/internal/generic_printer.h"
+ "strings/internal/generic_printer_internal.h"
"strings/internal/stl_type_traits.h"
"strings/internal/string_constant.h"
"strings/internal/stringify_sink.h"
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel
index c5bdd67..32ae277 100644
--- a/absl/log/internal/BUILD.bazel
+++ b/absl/log/internal/BUILD.bazel
@@ -554,6 +554,9 @@
hdrs = ["container.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl:__subpackages__",
+ ],
deps = [
"//absl/base:config",
"//absl/meta:requires",
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel
index 49051bd..a1e5021 100644
--- a/absl/strings/BUILD.bazel
+++ b/absl/strings/BUILD.bazel
@@ -1551,3 +1551,43 @@
"@googletest//:gtest_main",
],
)
+
+cc_library(
+ name = "generic_printer",
+ srcs = [
+ "internal/generic_printer.cc",
+ "internal/generic_printer_internal.h",
+ ],
+ hdrs = ["internal/generic_printer.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl:__subpackages__",
+ ],
+ deps = [
+ ":str_format",
+ ":strings",
+ "//absl/base:config",
+ "//absl/log/internal:container",
+ "//absl/meta:requires",
+ ],
+)
+
+cc_test(
+ name = "generic_printer_test",
+ srcs = ["internal/generic_printer_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":generic_printer",
+ ":strings",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/container:flat_hash_map",
+ "//absl/log",
+ "//absl/status",
+ "//absl/status:statusor",
+ "@googletest//:gtest",
+ "@googletest//:gtest_main",
+ ],
+)
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt
index 94e58ea..a03943d 100644
--- a/absl/strings/CMakeLists.txt
+++ b/absl/strings/CMakeLists.txt
@@ -1256,3 +1256,40 @@
absl::strings
GTest::gmock_main
)
+
+absl_cc_library(
+ NAME
+ generic_printer_internal
+ SRCS
+ "internal/generic_printer.cc"
+ "internal/generic_printer_internal.h"
+ HDRS
+ "internal/generic_printer.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::config
+ absl::strings
+ absl::str_format
+ absl::log_internal_container
+ absl::requires_internal
+)
+
+absl_cc_test(
+ NAME
+ generic_printer_test
+ SRCS
+ "internal/generic_printer_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::base
+ absl::config
+ absl::flat_hash_map
+ absl::generic_printer_internal
+ absl::log
+ absl::status
+ absl::statusor
+ absl::strings
+ GTest::gmock_main
+)
diff --git a/absl/strings/internal/generic_printer.cc b/absl/strings/internal/generic_printer.cc
new file mode 100644
index 0000000..16ca228
--- /dev/null
+++ b/absl/strings/internal/generic_printer.cc
@@ -0,0 +1,107 @@
+// Copyright 2025 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.
+
+#include "absl/strings/internal/generic_printer.h"
+
+#include <cstddef>
+#include <cstdlib>
+#include <ostream>
+#include <string>
+
+#include "absl/base/config.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_format.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace internal_generic_printer {
+
+// Out-of-line helper for PrintAsStringWithEscaping.
+std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v) {
+ return os << "\"" << absl::CHexEscape(v) << "\"";
+}
+
+// Retuns a string representation of 'v', shortened if possible.
+template <class T, class F>
+std::string TryShorten(T v, F strtox) {
+ std::string printed =
+ absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 / 2, v);
+ T parsed = strtox(printed.data());
+ if (parsed != v) {
+ printed =
+ absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 + 1, v);
+ }
+ return printed;
+}
+
+// Out-of-line helpers for floating point values. These don't necessarily
+// ensure that values are precise, but rather that they are wide enough to
+// represent distinct values. go/c++17std/numeric.limits.members.html
+std::ostream& PrintPreciseFP(std::ostream& os, float v) {
+ return os << TryShorten(v, [](const char* buf) {
+ char* unused;
+ return std::strtof(buf, &unused);
+ }) << "f";
+}
+std::ostream& PrintPreciseFP(std::ostream& os, double v) {
+ return os << TryShorten(v, [](const char* buf) {
+ char* unused;
+ return std::strtod(buf, &unused);
+ });
+}
+std::ostream& PrintPreciseFP(std::ostream& os, long double v) {
+ return os << TryShorten(v, [](const char* buf) {
+ char* unused;
+ return std::strtold(buf, &unused);
+ }) << "L";
+}
+
+// Prints a nibble of 'v' in hexadecimal.
+inline char hexnib(int v) {
+ return static_cast<char>((v < 10 ? '0' : ('a' - 10)) + v);
+}
+
+template <typename T>
+static std::ostream& PrintCharImpl(std::ostream& os, T v) {
+ // Specialization for chars: print as 'c' if printable, otherwise
+ // hex-escaped.
+ return (absl::ascii_isprint(static_cast<unsigned char>(v))
+ ? (os << (v == '\'' ? "'\\" : "'") << v)
+ : (os << "'\\x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf)))
+ << "' (0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf) << " "
+ << static_cast<int>(v) << ")";
+}
+
+std::ostream& PrintChar(std::ostream& os, char c) {
+ return PrintCharImpl(os, c);
+}
+
+std::ostream& PrintChar(std::ostream& os, signed char c) {
+ return PrintCharImpl(os, c);
+}
+
+std::ostream& PrintChar(std::ostream& os, unsigned char c) {
+ return PrintCharImpl(os, c);
+}
+
+std::ostream& PrintByte(std::ostream& os, std::byte b) {
+ auto v = std::to_integer<int>(b);
+ os << "0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf);
+ return os;
+}
+
+} // namespace internal_generic_printer
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/internal/generic_printer.h b/absl/strings/internal/generic_printer.h
new file mode 100644
index 0000000..ed60155
--- /dev/null
+++ b/absl/strings/internal/generic_printer.h
@@ -0,0 +1,115 @@
+// Copyright 2025 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_GENERIC_PRINTER_H_
+#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_
+
+#include "absl/strings/internal/generic_printer_internal.h" // IWYU pragma: export
+
+// Helpers for printing objects in generic code.
+//
+// These functions help with streaming in generic code. It may be desirable, for
+// example, to log values from generic functions; however, operator<< may not be
+// overloaded for all types.
+//
+// The helpers in this library use, in order of precedence:
+//
+// 1. std::string and std::string_view are quoted and escaped. (The specific
+// format is not guaranteed to be stable.)
+// 2. A defined AbslStringify() method.
+// 3. absl::log_internal::LogContainer, if the object is a STL container.
+// 4. For std::tuple, std::pair, and std::optional, recursively calls
+// GenericPrint for each element.
+// 5. Floating point values are printed with enough precision for round-trip.
+// 6. char values are quoted, with non-printable values escaped, and the
+// char's numeric value is included.
+// 7. A defined operator<< overload (which should be found by ADL).
+// 8. A defined DebugString() const method.
+// 9. A fallback value with basic information otherwise.
+//
+// Note that the fallback value means that if no formatting conversion is
+// defined, you will not see a compile-time error. This also means that
+// GenericPrint() can safely be used in generic template contexts, and can
+// format any types needed (even though the output will vary).
+//
+// Example usage:
+//
+// // All values after GenericPrint() are formatted:
+// LOG(INFO) << GenericPrint()
+// << int_var // <- printed normally
+// << float_var // <- sufficient precision for round-trip
+// << " unchanged literal text ";
+//
+// // Just one value is formatted:
+// LOG(INFO) << GenericPrint(string("this is quoted and escaped\t\n"))
+// << GenericPrint("but not this, ");
+// << string("and not this.");
+//
+// To make a type loggable with GenericPrint, prefer defining operator<< as a
+// friend function. For example:
+//
+// class TypeToLog {
+// public:
+// string ToString() const; // Many types already implement this.
+// // Define out-of-line if it is complex.
+// friend std::ostream& operator<<(std::ostream& os, const TypeToLog& v) {
+// return os << v.ToString(); // OK to define in-line instead, if simple.
+// }
+// };
+//
+// (Defining operator<< as an inline friend free function allows it to be found
+// by Argument-Dependent Lookup, or ADL, which is the mechanism typically used
+// for operator overload resolution. An inline friend function is the tightest
+// scope possible for overloading the left-hand side of an operator.)
+
+#include <ostream>
+#include <utility>
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace strings_internal {
+
+// Helper for logging values in generic code.
+//
+// Example usage:
+//
+// template <typename T>
+// void GenericFunction() {
+// T v1, v2;
+// VLOG(1) << GenericPrint() << v1 << v2; // GenericPrint everything
+// VLOG(1) << GenericPrint(v1) << v2; // GenericPrint just v1
+// }
+//
+inline constexpr internal_generic_printer::GenericPrintAdapterFactory
+ GenericPrint{};
+
+// Generic printer type: this class can be used, for example, as a template
+// argument to allow users to provide alternative printing strategies.
+//
+// For example, to allow callers to provide a custom strategy:
+//
+// template <typename T, typename PrinterT = GenericPrinter<T>>
+// void GenericFunction() {
+// T value;
+// VLOG(1) << PrinterT{value};
+// }
+//
+template <typename T>
+using GenericPrinter = internal_generic_printer::GenericPrinter<T>;
+
+} // namespace strings_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_
diff --git a/absl/strings/internal/generic_printer_internal.h b/absl/strings/internal/generic_printer_internal.h
new file mode 100644
index 0000000..1a2d0bd
--- /dev/null
+++ b/absl/strings/internal/generic_printer_internal.h
@@ -0,0 +1,423 @@
+// Copyright 2025 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_GENERIC_PRINTER_INTERNAL_H_
+#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "absl/base/config.h"
+#include "absl/log/internal/container.h"
+#include "absl/meta/internal/requires.h"
+#include "absl/strings/has_absl_stringify.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+
+// NOTE: we do not want to expand the dependencies of this library. All
+// compatibility detection must be done in a generic way, without having to
+// include the headers of other libraries.
+
+// Internal implementation details: see generic_printer.h.
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace internal_generic_printer {
+
+template <typename T>
+std::ostream& GenericPrintImpl(std::ostream& os, const T& v);
+
+// Scope blocker: we must always use ADL in our predicates below.
+struct Anonymous;
+std::ostream& operator<<(const Anonymous&, const Anonymous&) = delete;
+
+// Logging policy for LogContainer. Our SFINAE overload will not fail
+// if the contained type cannot be printed, so make sure to circle back to
+// GenericPrinter.
+struct ContainerLogPolicy {
+ void LogOpening(std::ostream& os) const { os << "["; }
+ void LogClosing(std::ostream& os) const { os << "]"; }
+ void LogFirstSeparator(std::ostream& os) const { os << ""; }
+ void LogSeparator(std::ostream& os) const { os << ", "; }
+ int64_t MaxElements() const { return 15; }
+ template <typename T>
+ void Log(std::ostream& os, const T& element) const {
+ internal_generic_printer::GenericPrintImpl(os, element);
+ }
+ void LogEllipsis(std::ostream& os) const { os << "..."; }
+};
+
+// Out-of-line helper for PrintAsStringWithEscaping.
+std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v);
+
+// Trait to recognize a string, possibly with a custom allocator.
+template <class T>
+inline constexpr bool is_any_string = false;
+template <class A>
+inline constexpr bool
+ is_any_string<std::basic_string<char, std::char_traits<char>, A>> = true;
+
+// Trait to recognize a supported pointer. Below are documented reasons why
+// raw pointers and std::shared_ptr are not currently supported.
+// See also http://b/239459272#comment9 and the discussion in the comment
+// threads of cl/485200996.
+//
+// The tl;dr:
+// 1. Pointers that logically represent an object (or nullptr) are safe to print
+// out.
+// 2. Pointers that may represent some other concept (delineating memory bounds,
+// overridden managed-memory deleters to mimic RAII, ...) may be unsafe to
+// print out.
+//
+// raw pointers:
+// - A pointer one-past the last element of an array
+// - Non-null but non-dereferenceable: https://gcc.godbolt.org/z/sqsqGvKbP
+//
+// `std::shared_ptr`:
+// - "Aliasing" / similar to raw pointers: https://gcc.godbolt.org/z/YbWPzvhae
+template <class T, class = void>
+inline constexpr bool is_supported_ptr = false;
+// `std::unique_ptr` has the same theoretical risks as raw pointers, but those
+// risks are far less likely (typically requiring a custom deleter), and the
+// benefits of supporting unique_ptr outweigh the costs.
+// - Note: `std::unique_ptr<T[]>` cannot safely be and is not dereferenced.
+template <class A, class... Deleter>
+inline constexpr bool is_supported_ptr<std::unique_ptr<A, Deleter...>> = true;
+// `ArenaSafeUniquePtr` is at least as safe as `std::unique_ptr`.
+template <class T>
+inline constexpr bool is_supported_ptr<
+ T,
+ // Check for `ArenaSafeUniquePtr` without having to include its header here.
+ // This does match any type named `ArenaSafeUniquePtr`, regardless of the
+ // namespace it is defined in, but it's pretty plausible that any such type
+ // would be a fork.
+ decltype(T().~ArenaSafeUniquePtr())> = true;
+
+// Specialization for floats: print floating point types using their
+// max_digits10 precision. This ensures each distinct underlying values
+// can be represented uniquely, even though it's not (strictly speaking)
+// the most precise representation.
+std::ostream& PrintPreciseFP(std::ostream& os, float v);
+std::ostream& PrintPreciseFP(std::ostream& os, double v);
+std::ostream& PrintPreciseFP(std::ostream& os, long double v);
+
+std::ostream& PrintChar(std::ostream& os, char c);
+std::ostream& PrintChar(std::ostream& os, signed char c);
+std::ostream& PrintChar(std::ostream& os, unsigned char c);
+
+std::ostream& PrintByte(std::ostream& os, std::byte b);
+
+template <class... Ts>
+std::ostream& PrintTuple(std::ostream& os, const std::tuple<Ts...>& tuple) {
+ absl::string_view sep = "";
+ const auto print_one = [&](const auto& v) {
+ os << sep;
+ (GenericPrintImpl)(os, v);
+ sep = ", ";
+ };
+ os << "<";
+ std::apply([&](const auto&... v) { (print_one(v), ...); }, tuple);
+ os << ">";
+ return os;
+}
+
+template <typename T, typename U>
+std::ostream& PrintPair(std::ostream& os, const std::pair<T, U>& p) {
+ os << "<";
+ (GenericPrintImpl)(os, p.first);
+ os << ", ";
+ (GenericPrintImpl)(os, p.second);
+ os << ">";
+ return os;
+}
+
+template <typename T>
+std::ostream& PrintOptionalLike(std::ostream& os, const T& v) {
+ if (v.has_value()) {
+ os << "<";
+ (GenericPrintImpl)(os, *v);
+ os << ">";
+ } else {
+ (GenericPrintImpl)(os, std::nullopt);
+ }
+ return os;
+}
+
+template <typename... Ts>
+std::ostream& PrintVariant(std::ostream& os, const std::variant<Ts...>& v) {
+ os << "(";
+ os << "'(index = " << v.index() << ")' ";
+
+ // NOTE(derekbailey): This may throw a std::bad_variant_access if the variant
+ // is "valueless", which only occurs if exceptions are thrown. This is
+ // non-relevant when not using exceptions, but it is worth mentioning if that
+ // invariant is ever changed.
+ std::visit([&](const auto& arg) { (GenericPrintImpl)(os, arg); }, v);
+ os << ")";
+ return os;
+}
+
+template <typename StatusOrLike>
+std::ostream& PrintStatusOrLike(std::ostream& os, const StatusOrLike& v) {
+ os << "<";
+ if (v.ok()) {
+ os << "OK: ";
+ (GenericPrintImpl)(os, *v);
+ } else {
+ (GenericPrintImpl)(os, v.status());
+ }
+ os << ">";
+ return os;
+}
+
+template <typename SmartPointer>
+std::ostream& PrintSmartPointerContents(std::ostream& os,
+ const SmartPointer& v) {
+ os << "<";
+ if (v == nullptr) {
+ (GenericPrintImpl)(os, nullptr);
+ } else {
+ // Cast to void* so that every type (e.g. `char*`) is printed as an address.
+ os << absl::implicit_cast<const void*>(v.get()) << " pointing to ";
+
+ if constexpr (meta_internal::Requires<SmartPointer>(
+ [](auto&& p) -> decltype(p[0]) {})) {
+ // e.g. std::unique_ptr<int[]>, which only has operator[]
+ os << "an array";
+ } else if constexpr (std::is_object_v<
+ typename SmartPointer::element_type>) {
+ (GenericPrintImpl)(os, *v);
+ } else {
+ // e.g. std::unique_ptr<void, MyCustomDeleter>
+ os << "a non-object type";
+ }
+ }
+ os << ">";
+ return os;
+}
+
+template <typename T>
+std::ostream& GenericPrintImpl(std::ostream& os, const T& v) {
+ if constexpr (is_any_string<T> || std::is_same_v<T, absl::string_view>) {
+ // Specialization for strings: prints with plausible quoting and escaping.
+ return PrintEscapedString(os, v);
+ } else if constexpr (absl::HasAbslStringify<T>::value) {
+ // If someone has specified `AbslStringify`, we should prefer that.
+ return os << absl::StrCat(v);
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w)
+ -> decltype((
+ PrintTuple)(std::declval<std::ostream&>(),
+ w)) {})) {
+ // For tuples, use `< elem0, ..., elemN >`.
+ return (PrintTuple)(os, v);
+
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w)
+ -> decltype((
+ PrintPair)(std::declval<std::ostream&>(),
+ w)) {})) {
+ // For pairs, use `< first, second >`.
+ return (PrintPair)(os, v);
+
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w)
+ -> decltype((
+ PrintVariant)(std::declval<std::ostream&>(),
+ w)) {})) {
+ // For std::variant, use `std::visit(v)`
+ return (PrintVariant)(os, v);
+ } else if constexpr (is_supported_ptr<T>) {
+ return (PrintSmartPointerContents)(os, v);
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w) -> decltype(w.ok(), w.status(), *w) {
+ })) {
+ return (PrintStatusOrLike)(os, v);
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w) -> decltype(w.has_value(), *w) {})) {
+ return (PrintOptionalLike)(os, v);
+ } else if constexpr (std::is_same_v<T, std::nullopt_t>) {
+ // Specialization for nullopt.
+ return os << "nullopt";
+
+ } else if constexpr (std::is_same_v<T, std::nullptr_t>) {
+ // Specialization for nullptr.
+ return os << "nullptr";
+
+ } else if constexpr (std::is_floating_point_v<T>) {
+ // For floating point print with enough precision for a roundtrip.
+ return PrintPreciseFP(os, v);
+
+ } else if constexpr (std::is_same_v<T, char> ||
+ std::is_same_v<T, signed char> ||
+ std::is_same_v<T, unsigned char>) {
+ // Chars are printed as the char (if a printable char) and the integral
+ // representation in hex and decimal.
+ return PrintChar(os, v);
+
+ } else if constexpr (std::is_same_v<T, std::byte>) {
+ return PrintByte(os, v);
+
+ } else if constexpr (std::is_same_v<T, bool> ||
+ std::is_same_v<T,
+ typename std::vector<bool>::reference> ||
+ std::is_same_v<
+ T, typename std::vector<bool>::const_reference>) {
+ return os << (v ? "true" : "false");
+
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w)
+ -> decltype(ProtobufInternalGetEnumDescriptor(
+ w)) {})) {
+ os << static_cast<std::underlying_type_t<T>>(v);
+ if (auto* desc =
+ ProtobufInternalGetEnumDescriptor(T{})->FindValueByNumber(v)) {
+ os << "(" << desc->name() << ")";
+ }
+ return os;
+ } else if constexpr (!std::is_enum_v<T> &&
+ meta_internal::Requires<const T>(
+ [&](auto&& w) -> decltype(absl::StrCat(w)) {})) {
+ return os << absl::StrCat(v);
+
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w)
+ -> decltype(std::declval<std::ostream&>()
+ << log_internal::LogContainer(w)) {
+ })) {
+ // For containers, use `[ elem0, ..., elemN ]` with a max of 15.
+ return os << log_internal::LogContainer(v, ContainerLogPolicy());
+
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w)
+ -> decltype(std::declval<std::ostream&>() << w) {
+ })) {
+ // Streaming
+ return os << v;
+
+ } else if constexpr (meta_internal::Requires<const T>(
+ [&](auto&& w)
+ -> decltype(std::declval<std::ostream&>()
+ << w.DebugString()) {})) {
+ // DebugString
+ return os << v.DebugString();
+
+ } else if constexpr (std::is_enum_v<T>) {
+ // In case the underlying type is some kind of char, we have to recurse.
+ return GenericPrintImpl(os, static_cast<std::underlying_type_t<T>>(v));
+
+ } else {
+ // Default: we don't have anything to stream the value.
+ return os << "[unprintable value of size " << sizeof(T) << " @" << &v
+ << "]";
+ }
+}
+
+// GenericPrinter always has a valid operator<<. It defers to the disjuction
+// above.
+template <typename T>
+class GenericPrinter {
+ public:
+ explicit GenericPrinter(const T& value) : value_(value) {}
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const GenericPrinter& gp) {
+ return internal_generic_printer::GenericPrintImpl(os, gp.value_);
+ }
+ const T& value_;
+};
+
+struct GenericPrintStreamAdapter {
+ template <class StreamT>
+ struct Impl {
+ // Stream operator: this object will adapt to the underlying stream type,
+ // but only if the Impl is an rvalue. For example, this works:
+ // std::cout << GenericPrint() << foo;
+ // but not:
+ // auto adapter = (std::cout << GenericPrint());
+ // adapter << foo;
+ template <typename T>
+ Impl&& operator<<(const T& value) && {
+ os << internal_generic_printer::GenericPrinter<T>(value);
+ return std::move(*this);
+ }
+
+ // Inhibit using a stack variable for the adapter:
+ template <typename T>
+ Impl& operator<<(const T& value) & = delete;
+
+ // Detects a Flush() method, for LogMessage compatibility.
+ template <typename T>
+ class HasFlushMethod {
+ private:
+ template <typename C>
+ static std::true_type Test(decltype(&C::Flush));
+ template <typename C>
+ static std::false_type Test(...);
+
+ public:
+ static constexpr bool value = decltype(Test<T>(nullptr))::value;
+ };
+
+ // LogMessage compatibility requires a Flush() method.
+ void Flush() {
+ if constexpr (HasFlushMethod<StreamT>::value) {
+ os.Flush();
+ }
+ }
+
+ StreamT& os;
+ };
+
+ // If Impl is evaluated on the RHS of an 'operator&&', and 'lhs && Impl.os'
+ // implicitly converts to void, then it's fine for Impl to do so, too. This
+ // will create precisely as many objects as 'lhs && Impl.os', so we should
+ // both observe any side effects, and avoid observing multiple side
+ // effects. (See absl::log_internal::Voidify for an example of why this might
+ // be useful.)
+ template <typename LHS, typename RHS>
+ friend auto operator&&(LHS&& lhs, Impl<RHS>&& rhs)
+ -> decltype(lhs && rhs.os) {
+ return lhs && rhs.os;
+ }
+
+ template <class StreamT>
+ friend Impl<StreamT> operator<<(StreamT& os, GenericPrintStreamAdapter&&) {
+ return Impl<StreamT>{os};
+ }
+};
+
+struct GenericPrintAdapterFactory {
+ internal_generic_printer::GenericPrintStreamAdapter operator()() const {
+ return internal_generic_printer::GenericPrintStreamAdapter{};
+ }
+ template <typename T>
+ internal_generic_printer::GenericPrinter<T> operator()(const T& value) const {
+ return internal_generic_printer::GenericPrinter<T>{value};
+ }
+};
+
+} // namespace internal_generic_printer
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_
diff --git a/absl/strings/internal/generic_printer_test.cc b/absl/strings/internal/generic_printer_test.cc
new file mode 100644
index 0000000..4093228
--- /dev/null
+++ b/absl/strings/internal/generic_printer_test.cc
@@ -0,0 +1,685 @@
+// Copyright 2025 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.
+
+#include "absl/strings/internal/generic_printer.h"
+
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <map>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/substitute.h"
+
+namespace generic_logging_test {
+struct NotStreamable {};
+} // namespace generic_logging_test
+
+static std::ostream& operator<<(std::ostream& os,
+ const generic_logging_test::NotStreamable&) {
+ return os << "This overload should NOT be found by GenericPrint.";
+}
+
+// Types to test selection logic for streamable and non-streamable types.
+namespace generic_logging_test {
+struct Streamable {
+ int x;
+ friend std::ostream& operator<<(std::ostream& os, const Streamable& l) {
+ return os << "Streamable{" << l.x << "}";
+ }
+};
+} // namespace generic_logging_test
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace strings_internal {
+namespace {
+
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::ContainsRegex;
+using ::testing::EndsWith;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+
+struct AbslStringifiable {
+ template <typename S>
+ friend void AbslStringify(S& sink, const AbslStringifiable&) {
+ sink.Append("AbslStringifiable!");
+ }
+};
+
+auto IsUnprintable() {
+#ifdef GTEST_USES_SIMPLE_RE
+ return HasSubstr("unprintable value of size");
+#else
+ return ContainsRegex(
+ "\\[unprintable value of size [0-9]+ @(0x)?[0-9a-fA-F]+\\]");
+#endif
+}
+
+auto HasExactlyNInstancesOf(int n, absl::string_view me) {
+#ifdef GTEST_USES_SIMPLE_RE
+ (void)n;
+ return HasSubstr(me);
+#else
+ absl::string_view value_m_times = "(.*$0){$1}.*";
+
+ return AllOf(MatchesRegex(absl::Substitute(value_m_times, me, n)),
+ Not(MatchesRegex(absl::Substitute(value_m_times, me, n + 1))));
+#endif
+}
+
+template <typename T>
+std::string GenericPrintToString(const T& v) {
+ std::stringstream ss;
+ ss << GenericPrint(v);
+ {
+ std::stringstream ss2;
+ ss2 << GenericPrint() << v;
+ EXPECT_EQ(ss.str(), ss2.str());
+ }
+ return ss.str();
+}
+
+TEST(GenericPrinterTest, Bool) {
+ EXPECT_EQ("true", GenericPrintToString(true));
+ EXPECT_EQ("false", GenericPrintToString(false));
+}
+
+TEST(GenericPrinterTest, VectorOfBool) {
+ std::vector<bool> v{true, false, true};
+ const auto& cv = v;
+ EXPECT_EQ("[true, false, true]", GenericPrintToString(v));
+ EXPECT_EQ("true", GenericPrintToString(v[0]));
+ EXPECT_EQ("true", GenericPrintToString(cv[0]));
+}
+
+TEST(GenericPrinterTest, CharLiterals) {
+ EXPECT_EQ(R"(a"\b)", GenericPrintToString(R"(a"\b)"));
+}
+
+TEST(GenericPrinterTest, Builtin) {
+ EXPECT_EQ("123", GenericPrintToString(123));
+}
+
+TEST(GenericPrinterTest, AbslStringifiable) {
+ EXPECT_EQ("AbslStringifiable!", GenericPrintToString(AbslStringifiable{}));
+}
+
+TEST(GenericPrinterTest, Nullptr) {
+ EXPECT_EQ("nullptr", GenericPrintToString(nullptr));
+}
+
+TEST(GenericPrinterTest, Chars) {
+ EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString('\x0a'));
+ EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(' '));
+ EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString('~'));
+ EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString('\''));
+}
+
+TEST(GenericPrinterTest, SignedChars) {
+ EXPECT_EQ(R"('\x0a' (0x0a 10))",
+ GenericPrintToString(static_cast<signed char>('\x0a')));
+ EXPECT_EQ(R"(' ' (0x20 32))",
+ GenericPrintToString(static_cast<signed char>(' ')));
+ EXPECT_EQ(R"('~' (0x7e 126))",
+ GenericPrintToString(static_cast<signed char>('~')));
+ EXPECT_EQ(R"('\'' (0x27 39))",
+ GenericPrintToString(static_cast<signed char>('\'')));
+}
+
+TEST(GenericPrinterTest, UnsignedChars) {
+ EXPECT_EQ(R"('\x0a' (0x0a 10))",
+ GenericPrintToString(static_cast<unsigned char>('\x0a')));
+ EXPECT_EQ(R"(' ' (0x20 32))",
+ GenericPrintToString(static_cast<unsigned char>(' ')));
+ EXPECT_EQ(R"('~' (0x7e 126))",
+ GenericPrintToString(static_cast<unsigned char>('~')));
+ EXPECT_EQ(R"('\'' (0x27 39))",
+ GenericPrintToString(static_cast<unsigned char>('\'')));
+}
+
+TEST(GenericPrinterTest, Bytes) {
+ EXPECT_EQ("0x00", GenericPrintToString(static_cast<std::byte>(0)));
+ EXPECT_EQ("0x7f", GenericPrintToString(static_cast<std::byte>(0x7F)));
+ EXPECT_EQ("0xff", GenericPrintToString(static_cast<std::byte>(0xFF)));
+}
+
+TEST(GenericPrinterTest, Strings) {
+ const std::string expected_quotes = R"("a\"\\b")";
+ EXPECT_EQ(expected_quotes, GenericPrintToString(std::string(R"(a"\b)")));
+ const std::string expected_nonprintable = R"("\x00\xcd\n\xab")";
+ EXPECT_EQ(expected_nonprintable,
+ GenericPrintToString(absl::string_view("\0\315\n\xAB", 4)));
+}
+
+TEST(GenericPrinterTest, PreciseFloat) {
+ // Instead of testing exactly how the values are formatted, just check that
+ // they are distinct.
+
+ // Ensure concise output for exact values:
+ EXPECT_EQ("1f", GenericPrintToString(1.f));
+ EXPECT_EQ("1.1f", GenericPrintToString(1.1f));
+
+ // Plausible real-world values:
+ float f = 10.0000095f;
+ EXPECT_NE(GenericPrintToString(f), GenericPrintToString(10.0000105f));
+ // Smallest increment for a real-world value:
+ EXPECT_NE(GenericPrintToString(f),
+ GenericPrintToString(std::nextafter(f, 11)));
+ // The two smallest (finite) values possible:
+ EXPECT_NE(GenericPrintToString(std::numeric_limits<float>::lowest()),
+ GenericPrintToString(
+ std::nextafter(std::numeric_limits<float>::lowest(), 1)));
+ // Ensure the value has the correct type suffix:
+ EXPECT_THAT(GenericPrintToString(0.f), EndsWith("f"));
+}
+
+TEST(GenericPrinterTest, PreciseDouble) {
+ EXPECT_EQ("1", GenericPrintToString(1.));
+ EXPECT_EQ("1.1", GenericPrintToString(1.1));
+ double d = 10.000000000000002;
+ EXPECT_NE(GenericPrintToString(d), GenericPrintToString(10.000000000000004));
+ EXPECT_NE(GenericPrintToString(d),
+ GenericPrintToString(std::nextafter(d, 11)));
+ EXPECT_NE(GenericPrintToString(std::numeric_limits<double>::lowest()),
+ GenericPrintToString(
+ std::nextafter(std::numeric_limits<double>::lowest(), 1)));
+ EXPECT_THAT(GenericPrintToString(0.), EndsWith("0"));
+}
+
+TEST(GenericPrinterTest, PreciseLongDouble) {
+ EXPECT_EQ("1L", GenericPrintToString(1.L));
+ EXPECT_EQ("1.1L", GenericPrintToString(1.1L));
+ long double ld = 10.0000000000000000000000000000002;
+ EXPECT_NE(GenericPrintToString(ld),
+ GenericPrintToString(10.0000000000000000000000000000004));
+ EXPECT_NE(GenericPrintToString(ld),
+ GenericPrintToString(std::nextafter(ld, 11)));
+ EXPECT_NE(GenericPrintToString(std::numeric_limits<long double>::lowest()),
+ GenericPrintToString(
+ std::nextafter(std::numeric_limits<long double>::lowest(), 1)));
+ EXPECT_THAT(GenericPrintToString(0.L), EndsWith("L"));
+}
+
+TEST(GenericPrinterTest, StreamableLvalue) {
+ generic_logging_test::Streamable x{234};
+ EXPECT_EQ("Streamable{234}", GenericPrintToString(x));
+}
+
+TEST(GenericPrinterTest, StreamableXvalue) {
+ EXPECT_EQ("Streamable{345}",
+ GenericPrintToString(generic_logging_test::Streamable{345}));
+}
+
+TEST(GenericPrinterTest, NotStreamableWithoutGenericPrint) {
+ ::generic_logging_test::NotStreamable x;
+ std::stringstream ss;
+ ::operator<<(ss, x);
+ EXPECT_EQ(ss.str(), "This overload should NOT be found by GenericPrint.");
+}
+
+TEST(GenericPrinterTest, NotStreamableLvalue) {
+ generic_logging_test::NotStreamable x;
+ EXPECT_THAT(GenericPrintToString(x), IsUnprintable());
+}
+
+TEST(GenericPrinterTest, NotStreamableXvalue) {
+ EXPECT_THAT(GenericPrintToString(generic_logging_test::NotStreamable{}),
+ IsUnprintable());
+}
+
+TEST(GenericPrinterTest, DebugString) {
+ struct WithDebugString {
+ std::string val;
+ std::string DebugString() const {
+ return absl::StrCat("WithDebugString{", val, "}");
+ }
+ };
+ EXPECT_EQ("WithDebugString{foo}",
+ GenericPrintToString(WithDebugString{"foo"}));
+}
+
+TEST(GenericPrinterTest, Vector) {
+ std::vector<int> v = {4, 5, 6};
+ EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*4,? 5,? 6.*"));
+}
+
+TEST(GenericPrinterTest, StreamableVector) {
+ std::vector<generic_logging_test::Streamable> v = {{7}, {8}, {9}};
+ EXPECT_THAT(GenericPrintToString(v),
+ MatchesRegex(".*Streamable.7.,? Streamable.8.,? Streamable.9.*"));
+}
+
+TEST(GenericPrinterTest, Map) {
+ absl::flat_hash_map<
+ std::string, absl::flat_hash_map<std::string, std::pair<double, double>>>
+ v = {{"A", {{"B", {.5, .25}}}}};
+
+ EXPECT_THAT(GenericPrintToString(v), R"([<"A", [<"B", <0.5, 0.25>>]>])");
+
+ std::map<std::string, std::map<std::string, std::pair<double, double>>> v2 = {
+ {"A", {{"B", {.5, .25}}}}};
+
+ EXPECT_THAT(GenericPrintToString(v2), R"([<"A", [<"B", <0.5, 0.25>>]>])");
+}
+
+TEST(GenericPrinterTest, StreamAdapter) {
+ std::stringstream ss;
+ static_assert(
+ std::is_same<
+ typename std::remove_reference<decltype(ss << GenericPrint())>::type,
+ internal_generic_printer::GenericPrintStreamAdapter::Impl<
+ std::stringstream>>::value,
+ "expected ostream << GenericPrint() to yield adapter impl");
+
+ ss << GenericPrint() << "again, " << "back-up, " << "cue, "
+ << "double-u, " << "eye, "
+ << "four: " << generic_logging_test::NotStreamable{};
+ EXPECT_THAT(
+ ss.str(),
+ MatchesRegex(
+ "again, back-up, cue, double-u, eye, four: .unprintable value.*"));
+}
+
+TEST(GenericPrinterTest, NotStreamableVector) {
+ std::vector<generic_logging_test::NotStreamable> v = {{}, {}, {}};
+#ifdef GTEST_USES_SIMPLE_RE
+ EXPECT_THAT(GenericPrintToString(v), HasSubstr("unprintable"));
+#else
+ EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*(unprintable.*){3}.*"));
+#endif
+}
+
+struct CustomContainer : public std::array<int, 4> {
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const CustomContainer& c) {
+ absl::Format(&sink, "%d %d", c[0], c[1]);
+ }
+};
+
+// Checks that AbslStringify (go/totw/215) is respected for container-like
+// types.
+TEST(GenericPrinterTest, ContainerLikeCustomLogging) {
+ CustomContainer c = {1, 2, 3, 4};
+ EXPECT_EQ(GenericPrintToString(c), "1 2");
+}
+
+// Test helper: this function demonstrates customizable printing logic:
+// 'GenericPrinter<T>' can be nominated as a default template argument.
+template <typename T, typename Printer = GenericPrinter<T>>
+std::string SpecializablePrint(const T& v) {
+ std::stringstream ss;
+ ss << Printer{v};
+ return ss.str();
+}
+
+TEST(GenericPrinterTest, DefaultPrinter) {
+ EXPECT_EQ("123", SpecializablePrint(123));
+}
+
+// Example of custom printing logic. This doesn't actually test anything in
+// GenericPrinter, but it's a working example of customizing printing logic (as
+// opposed to the comments in generic_printer.h).
+struct CustomPrinter {
+ explicit CustomPrinter(int) {}
+ friend std::ostream& operator<<(std::ostream& os, CustomPrinter&&) {
+ return os << "custom printer";
+ }
+};
+
+TEST(GenericPrinterTest, CustomPrinter) {
+ EXPECT_EQ("custom printer", (SpecializablePrint<int, CustomPrinter>(123)));
+}
+
+TEST(GenricPrinterTest, Nullopt) {
+ EXPECT_EQ("nullopt", GenericPrintToString(std::nullopt));
+}
+
+TEST(GenericPrinterTest, Optional) {
+ EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>()));
+ EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>(std::nullopt)));
+ EXPECT_EQ("<3>", GenericPrintToString(std::make_optional(3)));
+ EXPECT_EQ("<Streamable{3}>", GenericPrintToString(std::make_optional(
+ generic_logging_test::Streamable{3})));
+}
+
+TEST(GenericPrinterTest, Tuple) {
+ EXPECT_EQ("<1, two, 3>", GenericPrintToString(std::make_tuple(1, "two", 3)));
+}
+
+TEST(GenericPrinterTest, EmptyTuple) {
+ EXPECT_EQ("<>", GenericPrintToString(std::make_tuple()));
+}
+
+TEST(GenericPrinterTest, TupleWithStreamableMember) {
+ EXPECT_EQ("<1, two, Streamable{3}>",
+ GenericPrintToString(std::make_tuple(
+ 1, "two", generic_logging_test::Streamable{3})));
+}
+
+TEST(GenericPrinterTest, Variant) {
+ EXPECT_EQ(R"(('(index = 0)' "cow"))",
+ GenericPrintToString(std::variant<std::string, float>("cow")));
+
+ EXPECT_EQ("('(index = 1)' 1.1f)",
+ GenericPrintToString(std::variant<std::string, float>(1.1F)));
+}
+
+TEST(GenericPrinterTest, VariantMonostate) {
+ EXPECT_THAT(GenericPrintToString(std::variant<std::monostate, std::string>()),
+ IsUnprintable());
+}
+
+TEST(GenericPrinterTest, VariantNonStreamable) {
+ EXPECT_EQ(R"(('(index = 0)' "cow"))",
+ GenericPrintToString(
+ std::variant<std::string, generic_logging_test::NotStreamable>(
+ "cow")));
+
+ EXPECT_THAT(
+ GenericPrintToString(
+ std::variant<std::string, generic_logging_test::NotStreamable>(
+ generic_logging_test::NotStreamable{})),
+ IsUnprintable());
+}
+
+TEST(GenericPrinterTest, VariantNestedVariant) {
+ EXPECT_EQ(
+ "('(index = 1)' ('(index = 1)' 1.1f))",
+ GenericPrintToString(std::variant<std::string, std::variant<int, float>>(
+ std::variant<int, float>(1.1F))));
+}
+
+TEST(GenericPrinterTest, VariantInPlace) {
+ EXPECT_EQ("('(index = 0)' 17)", GenericPrintToString(std::variant<int, int>(
+ std::in_place_index<0>, 17)));
+
+ EXPECT_EQ("('(index = 1)' 17)", GenericPrintToString(std::variant<int, int>(
+ std::in_place_index<1>, 17)));
+}
+
+TEST(GenericPrinterTest, StatusOrLikeOkPrintsValue) {
+ EXPECT_EQ(R"(<OK: "cow">)",
+ GenericPrintToString(absl::StatusOr<std::string>("cow")));
+
+ EXPECT_EQ(R"(<OK: 1.1f>)", GenericPrintToString(absl::StatusOr<float>(1.1F)));
+}
+
+TEST(GenericPrinterTest, StatusOrLikeNonOkPrintsStatus) {
+ EXPECT_THAT(
+ GenericPrintToString(absl::StatusOr<float>(
+ absl::InvalidArgumentError("my error message"))),
+ AllOf(HasSubstr("my error message"), HasSubstr("INVALID_ARGUMENT")));
+
+ EXPECT_THAT(GenericPrintToString(
+ absl::StatusOr<int>(absl::AbortedError("other message"))),
+ AllOf(HasSubstr("other message"), HasSubstr("ABORTED")));
+}
+
+TEST(GenericPrinterTest, StatusOrLikeNonStreamableValueUnprintable) {
+ EXPECT_THAT(
+ GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>(
+ generic_logging_test::NotStreamable{})),
+ IsUnprintable());
+}
+
+TEST(GenericPrinterTest, StatusOrLikeNonStreamableErrorStillPrintable) {
+ EXPECT_THAT(
+ GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>(
+ absl::AbortedError("other message"))),
+ AllOf(HasSubstr("other message"), HasSubstr("ABORTED")));
+}
+
+TEST(GenericPrinterTest, IsSupportedPointer) {
+ using internal_generic_printer::is_supported_ptr;
+
+ EXPECT_TRUE(is_supported_ptr<std::unique_ptr<std::string>>);
+ EXPECT_TRUE(is_supported_ptr<std::unique_ptr<int[]>>);
+ EXPECT_TRUE((is_supported_ptr<std::unique_ptr<void, void (*)(void*)>>));
+
+ EXPECT_FALSE(is_supported_ptr<int*>);
+ EXPECT_FALSE(is_supported_ptr<std::shared_ptr<int>>);
+ EXPECT_FALSE(is_supported_ptr<std::weak_ptr<int>>);
+}
+
+TEST(GenericPrinterTest, SmartPointerPrintsNullptrForAllNullptrs) {
+ std::unique_ptr<std::string> up;
+
+ EXPECT_EQ("<nullptr>", GenericPrintToString(up));
+}
+
+TEST(GenericPrinterTest, SmartPointerPrintsValueIfNonNull) {
+ EXPECT_THAT(GenericPrintToString(std::make_unique<int>(5)),
+ HasSubstr("pointing to 5"));
+}
+
+TEST(GenericPrinterTest, SmartPointerPrintsAddressOfPointee) {
+ auto i = std::make_unique<int>(5);
+ auto c = std::make_unique<char>('z');
+ char memory[] = "abcdefg";
+ auto cp = std::make_unique<char*>(memory);
+
+ EXPECT_THAT(GenericPrintToString(i),
+ AnyOf(Eq(absl::StrFormat("<%016X pointing to 5>",
+ reinterpret_cast<intptr_t>(&*i))),
+ Eq(absl::StrFormat("<%#x pointing to 5>",
+ reinterpret_cast<intptr_t>(&*i)))));
+
+ EXPECT_THAT(
+ GenericPrintToString(c),
+ AnyOf(HasSubstr(absl::StrFormat("<%016X pointing to 'z'",
+ reinterpret_cast<intptr_t>(&*c))),
+ HasSubstr(absl::StrFormat("<%#x pointing to 'z'",
+ reinterpret_cast<intptr_t>(&*c)))));
+
+ EXPECT_THAT(GenericPrintToString(cp),
+ AnyOf(Eq(absl::StrFormat("<%016X pointing to abcdefg>",
+ reinterpret_cast<intptr_t>(&*cp))),
+ Eq(absl::StrFormat("<%#x pointing to abcdefg>",
+ reinterpret_cast<intptr_t>(&*cp)))));
+}
+
+TEST(GenericPrinterTest, SmartPointerToArrayOnlyPrintsAddressAndHelpText) {
+ auto empty = std::make_unique<int[]>(0);
+ auto nonempty = std::make_unique<int[]>(5);
+ nonempty[0] = 12345;
+ nonempty[4] = 54321;
+ // NOTE: ArenaSafeUniquePtr is not meant to support array-type template
+ // parameters, so we skip testing that here.
+ // http://g/c-users/J-AEFrFHssY/UMMFzCkdBAAJ, b/265984185.
+
+ EXPECT_THAT(
+ GenericPrintToString(nonempty),
+ AllOf(AnyOf(HasSubstr(absl::StrFormat(
+ "%016X", reinterpret_cast<intptr_t>(nonempty.get()))),
+ HasSubstr(absl::StrFormat(
+ "%#x", reinterpret_cast<intptr_t>(nonempty.get())))),
+ HasSubstr("array"), Not(HasSubstr("to 54321")),
+ Not(HasSubstr("to 12345"))));
+
+ EXPECT_THAT(
+ GenericPrintToString(empty),
+ AllOf(AnyOf(HasSubstr(absl::StrFormat(
+ "%016X", reinterpret_cast<intptr_t>(empty.get()))),
+ HasSubstr(absl::StrFormat(
+ "%#x", reinterpret_cast<intptr_t>(empty.get())))),
+ HasSubstr("array")));
+}
+
+TEST(GenericPrinterTest, SmartPointerToNonObjectType) {
+ auto int_ptr_deleter = [](void* data) {
+ int* p = static_cast<int*>(data);
+ delete p;
+ };
+
+ std::unique_ptr<void, decltype(int_ptr_deleter)> void_ptr(new int(959),
+ int_ptr_deleter);
+
+ EXPECT_THAT(GenericPrintToString(void_ptr),
+ HasSubstr("pointing to a non-object type"));
+}
+
+TEST(GenericPrinterTest, PrintsCustomDeleterSmartPointer) {
+ // Delete `p` (if not nullptr) only on the 4th time the deleter is used.
+ auto four_deleter = [](std::string* p) {
+ static int counter = 0;
+ if (p == nullptr) return; // skip calls to moved-from destructors.
+ if (++counter >= 4) delete p;
+ };
+
+ // Have four `unique_ptr`s "manage" the same string-pointer, with only the
+ // final (4th) call to the deleter deleting the string pointer.
+ auto* unique_string = new std::string("unique string");
+ std::vector<std::unique_ptr<std::string, decltype(four_deleter)>> test_ptrs;
+ for (int i = 0; i < 4; ++i) {
+ test_ptrs.emplace_back(unique_string, four_deleter);
+ }
+
+ EXPECT_THAT(GenericPrintToString(test_ptrs),
+ HasExactlyNInstancesOf(4, "unique string"));
+}
+
+// Ensure that GenericPrint is robust to recursion when a type's operator<<
+// calls into GenericPrint internally.
+struct CustomRecursive {
+ std::unique_ptr<CustomRecursive> next;
+ int val = 0;
+
+ friend std::ostream& operator<<(std::ostream& os, const CustomRecursive& cr) {
+ return os << "custom print: next = " << GenericPrintToString(cr.next);
+ }
+};
+
+TEST(GenericPrinterTest, DISABLED_CustomPrintOverloadRecursionDetected) {
+ auto r1 = std::make_unique<CustomRecursive>();
+ r1->val = 1;
+ auto& r2 = r1->next = std::make_unique<CustomRecursive>();
+ r2->val = 2;
+ r2->next = std::move(r1);
+
+ EXPECT_THAT(GenericPrintToString(*r2),
+ AllOf(HasExactlyNInstancesOf(2, "custom print"),
+ HasExactlyNInstancesOf(1, "<recursive>")));
+
+ r2->next = nullptr; // break the cycle
+}
+// <end DISABLED_ test section>
+
+enum CStyleEnum { kValue0, kValue1 };
+TEST(GenericPrinterTest, Enum) {
+ EXPECT_EQ("1", GenericPrintToString(kValue1));
+}
+
+enum class CppStyleEnum { kValue0, kValue1, kValue2 };
+TEST(GenericPrinterTest, EnumClass) {
+ EXPECT_EQ("2", GenericPrintToString(CppStyleEnum::kValue2));
+}
+
+enum class CharBasedEnum : char { kValueA = 'A', kValue1 = '\x01' };
+TEST(GenericPrinterTest, CharBasedEnum) {
+ EXPECT_EQ("'A' (0x41 65)", GenericPrintToString(CharBasedEnum::kValueA));
+ EXPECT_EQ("'\\x01' (0x01 1)", GenericPrintToString(CharBasedEnum::kValue1));
+}
+
+enum class WideBasedEnum : uint64_t {
+ kValue = std::numeric_limits<uint64_t>::max()
+};
+TEST(GenericPrinterTest, WideBasedEnum) {
+ EXPECT_EQ(absl::StrCat(std::numeric_limits<uint64_t>::max()),
+ GenericPrintToString(WideBasedEnum::kValue));
+}
+
+enum CStyleEnumWithStringify { kValueA = 0, kValueB = 2 };
+template <typename Sink>
+void AbslStringify(Sink& sink, CStyleEnumWithStringify e) {
+ switch (e) {
+ case CStyleEnumWithStringify::kValueA:
+ sink.Append("A");
+ return;
+ case CStyleEnumWithStringify::kValueB:
+ sink.Append("B");
+ return;
+ }
+ sink.Append("??");
+}
+TEST(GenericPrinterTest, CStyleEnumWithStringify) {
+ EXPECT_EQ("A", GenericPrintToString(CStyleEnumWithStringify::kValueA));
+ EXPECT_EQ("??",
+ GenericPrintToString(static_cast<CStyleEnumWithStringify>(1)));
+}
+
+enum class CppStyleEnumWithStringify { kValueA, kValueB, kValueC };
+template <typename Sink>
+void AbslStringify(Sink& sink, CppStyleEnumWithStringify e) {
+ switch (e) {
+ case CppStyleEnumWithStringify::kValueA:
+ sink.Append("A");
+ return;
+ case CppStyleEnumWithStringify::kValueB:
+ sink.Append("B");
+ return;
+ case CppStyleEnumWithStringify::kValueC:
+ sink.Append("C");
+ return;
+ }
+ sink.Append("??");
+}
+TEST(GenericPrinterTest, CppStyleEnumWithStringify) {
+ EXPECT_EQ("A", GenericPrintToString(CppStyleEnumWithStringify::kValueA));
+ EXPECT_EQ("??",
+ GenericPrintToString(static_cast<CppStyleEnumWithStringify>(17)));
+}
+
+enum class CharBasedEnumWithStringify : char { kValueA = 'A', kValueB = 'B' };
+template <typename Sink>
+void AbslStringify(Sink& sink, CharBasedEnumWithStringify e) {
+ switch (e) {
+ case CharBasedEnumWithStringify::kValueA:
+ sink.Append("charA");
+ return;
+ case CharBasedEnumWithStringify::kValueB:
+ sink.Append("charB");
+ return;
+ }
+ sink.Append("??");
+}
+TEST(GenericPrinterTest, CharBasedEnumWithStringify) {
+ EXPECT_EQ("charA", GenericPrintToString(CharBasedEnumWithStringify::kValueA));
+ EXPECT_EQ("??",
+ GenericPrintToString(static_cast<CharBasedEnumWithStringify>('W')));
+}
+
+} // namespace
+} // namespace strings_internal
+ABSL_NAMESPACE_END
+} // namespace absl