| // 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; | |
| // `proto2::Arena::UniquePtr` is at least as safe as `std::unique_ptr`. | |
| template <class T> | |
| 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_t<decltype( | |
| // Check the class name using the destructor. | |
| T().~UniquePtr(), | |
| // Check some other members, to try to duck type into the right class. | |
| T().get(), T().reset(), T().try_heap_release(), | |
| T().GetOwningArena() | |
| ->template MakeUnique<int>(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 <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 (is_supported_ptr<T>) { | |
| return (PrintSmartPointerContents)(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 (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_same_v<T, std::monostate>) { | |
| // Specialization for `std::monostate`. | |
| return os << "monostate"; | |
| } 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_ |