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