// 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 #include #include #include #include #include #include #include #include #include #include #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 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 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 inline constexpr bool is_any_string = false; template inline constexpr bool is_any_string, 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 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` cannot safely be and is not dereferenced. template inline constexpr bool is_supported_ptr> = true; // `ArenaSafeUniquePtr` is at least as safe as `std::unique_ptr`. template 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; // `proto2::Arena::UniquePtr` is at least as safe as `std::unique_ptr`. template inline constexpr bool is_supported_ptr< T, // Check for `proto2::Arena::UniquePtr` without having to include its // header here. This does match any type named `UniquePtr`, regardless // of the scope it is defined in, but we try to restrict by probing some // methods. std::void_ttemplate MakeUnique(nullptr) .~UniquePtr())>> = 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 std::ostream& PrintTuple(std::ostream& os, const std::tuple& 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 std::ostream& PrintPair(std::ostream& os, const std::pair& p) { os << "<"; (GenericPrintImpl)(os, p.first); os << ", "; (GenericPrintImpl)(os, p.second); os << ">"; return os; } template 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 std::ostream& PrintVariant(std::ostream& os, const std::variant& 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 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 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(v.get()) << " pointing to "; if constexpr (meta_internal::Requires( [](auto&& p) -> decltype(p[0]) {})) { // e.g. std::unique_ptr, 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 os << "a non-object type"; } } os << ">"; return os; } template std::ostream& GenericPrintImpl(std::ostream& os, const T& v) { if constexpr (is_any_string || std::is_same_v) { // Specialization for strings: prints with plausible quoting and escaping. return PrintEscapedString(os, v); } else if constexpr (is_supported_ptr) { return (PrintSmartPointerContents)(os, v); } else if constexpr (absl::HasAbslStringify::value) { // If someone has specified `AbslStringify`, we should prefer that. return os << absl::StrCat(v); } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(( PrintTuple)(std::declval(), w)) {})) { // For tuples, use `< elem0, ..., elemN >`. return (PrintTuple)(os, v); } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(( PrintPair)(std::declval(), w)) {})) { // For pairs, use `< first, second >`. return (PrintPair)(os, v); } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(( PrintVariant)(std::declval(), w)) {})) { // For std::variant, use `std::visit(v)` return (PrintVariant)(os, v); } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(w.ok(), w.status(), *w) { })) { return (PrintStatusOrLike)(os, v); } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(w.has_value(), *w) {})) { return (PrintOptionalLike)(os, v); } else if constexpr (std::is_same_v) { // Specialization for nullopt. return os << "nullopt"; } else if constexpr (std::is_same_v) { // Specialization for nullptr. return os << "nullptr"; } else if constexpr (std::is_same_v) { // Specialization for `std::monostate`. return os << "monostate"; } else if constexpr (std::is_floating_point_v) { // For floating point print with enough precision for a roundtrip. return PrintPreciseFP(os, v); } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { // 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) { return PrintByte(os, v); } else if constexpr (std::is_same_v || std::is_same_v::reference> || std::is_same_v< T, typename std::vector::const_reference>) { return os << (v ? "true" : "false"); } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(ProtobufInternalGetEnumDescriptor( w)) {})) { os << static_cast>(v); if (auto* desc = ProtobufInternalGetEnumDescriptor(T{})->FindValueByNumber(v)) { os << "(" << desc->name() << ")"; } return os; } else if constexpr (!std::is_enum_v && meta_internal::Requires( [&](auto&& w) -> decltype(absl::StrCat(w)) {})) { return os << absl::StrCat(v); } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(std::declval() << 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( [&](auto&& w) -> decltype(std::declval() << w) { })) { // Streaming return os << v; } else if constexpr (meta_internal::Requires( [&](auto&& w) -> decltype(std::declval() << w.DebugString()) {})) { // DebugString return os << v.DebugString(); } else if constexpr (std::is_enum_v) { // In case the underlying type is some kind of char, we have to recurse. return GenericPrintImpl(os, static_cast>(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 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 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 Impl&& operator<<(const T& value) && { os << internal_generic_printer::GenericPrinter(value); return std::move(*this); } // Inhibit using a stack variable for the adapter: template Impl& operator<<(const T& value) & = delete; // Detects a Flush() method, for LogMessage compatibility. template class HasFlushMethod { private: template static std::true_type Test(decltype(&C::Flush)); template static std::false_type Test(...); public: static constexpr bool value = decltype(Test(nullptr))::value; }; // LogMessage compatibility requires a Flush() method. void Flush() { if constexpr (HasFlushMethod::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 friend auto operator&&(LHS&& lhs, Impl&& rhs) -> decltype(lhs && rhs.os) { return lhs && rhs.os; } template friend Impl operator<<(StreamT& os, GenericPrintStreamAdapter&&) { return Impl{os}; } }; struct GenericPrintAdapterFactory { internal_generic_printer::GenericPrintStreamAdapter operator()() const { return internal_generic_printer::GenericPrintStreamAdapter{}; } template internal_generic_printer::GenericPrinter operator()(const T& value) const { return internal_generic_printer::GenericPrinter{value}; } }; } // namespace internal_generic_printer ABSL_NAMESPACE_END } // namespace absl #endif // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_