1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef BASE_STRINGS_TO_STRING_H_
6 #define BASE_STRINGS_TO_STRING_H_
7
8 #include <concepts>
9 #include <ios>
10 #include <memory>
11 #include <sstream>
12 #include <string>
13 #include <tuple>
14 #include <type_traits>
15 #include <utility>
16
17 #include "base/types/supports_ostream_operator.h"
18
19 namespace base {
20
21 template <typename... Ts>
22 std::string ToString(const Ts&... values);
23
24 namespace internal {
25
26 template <typename T>
requires(const T & t)27 concept SupportsToString = requires(const T& t) { t.ToString(); };
28
29 // I/O manipulators are function pointers, but should be sent directly to the
30 // `ostream` instead of being cast to `const void*` like other function
31 // pointers.
32 template <typename T>
33 constexpr bool IsIomanip = false;
34 template <typename T>
35 requires(std::derived_from<T, std::ios_base>)
36 constexpr bool IsIomanip<T&(T&)> = true;
37
38 // Function pointers implicitly convert to `bool`, so use this to avoid printing
39 // function pointers as 1 or 0.
40 template <typename T>
41 concept WillBeIncorrectlyStreamedAsBool =
42 std::is_function_v<std::remove_pointer_t<T>> &&
43 !IsIomanip<std::remove_pointer_t<T>>;
44
45 // Fallback case when there is no better representation.
46 template <typename T>
47 struct ToStringHelper {
StringifyToStringHelper48 static void Stringify(const T& v, std::ostringstream& ss) {
49 // We cast to `void*` to avoid converting a char-like type to char-like*
50 // which operator<< treats as a string but does not support for multi-byte
51 // char-like types.
52 ss << "[" << sizeof(v) << "-byte object at 0x"
53 << static_cast<const void*>(std::addressof(v)) << "]";
54 }
55 };
56
57 // Most streamables.
58 template <typename T>
59 requires(SupportsOstreamOperator<const T&> &&
60 !WillBeIncorrectlyStreamedAsBool<T>)
61 struct ToStringHelper<T> {
62 static void Stringify(const T& v, std::ostringstream& ss) { ss << v; }
63 };
64
65 // Functions and function pointers.
66 template <typename T>
67 requires(SupportsOstreamOperator<const T&> &&
68 WillBeIncorrectlyStreamedAsBool<T>)
69 struct ToStringHelper<T> {
70 static void Stringify(const T& v, std::ostringstream& ss) {
71 ToStringHelper<const void*>::Stringify(reinterpret_cast<const void*>(v),
72 ss);
73 }
74 };
75
76 // Integral types that can't stream, like char16_t.
77 template <typename T>
78 requires(!SupportsOstreamOperator<const T&> && std::is_integral_v<T>)
79 struct ToStringHelper<T> {
80 static void Stringify(const T& v, std::ostringstream& ss) {
81 if constexpr (std::is_signed_v<T>) {
82 static_assert(sizeof(T) <= 8);
83 ss << static_cast<int64_t>(v);
84 } else {
85 static_assert(sizeof(T) <= 8);
86 ss << static_cast<uint64_t>(v);
87 }
88 }
89 };
90
91 // Non-streamables that have a `ToString` member.
92 template <typename T>
93 requires(!SupportsOstreamOperator<const T&> && SupportsToString<const T&>)
94 struct ToStringHelper<T> {
95 static void Stringify(const T& v, std::ostringstream& ss) {
96 // .ToString() may not return a std::string, e.g. blink::WTF::String.
97 ToStringHelper<decltype(v.ToString())>::Stringify(v.ToString(), ss);
98 }
99 };
100
101 // Non-streamable enums (i.e. scoped enums where no `operator<<` overload was
102 // declared).
103 template <typename T>
104 requires(!SupportsOstreamOperator<const T&> && std::is_enum_v<T>)
105 struct ToStringHelper<T> {
106 static void Stringify(const T& v, std::ostringstream& ss) {
107 using UT = typename std::underlying_type_t<T>;
108 ToStringHelper<UT>::Stringify(static_cast<UT>(v), ss);
109 }
110 };
111
112 // Tuples. Will recursively apply `ToString()` to each value in the tuple.
113 template <typename... T>
114 struct ToStringHelper<std::tuple<T...>> {
115 template <size_t... I>
116 static void StringifyHelper(const std::tuple<T...>& values,
117 std::index_sequence<I...>,
118 std::ostringstream& ss) {
119 ss << "<";
120 (..., (ss << (I == 0 ? "" : ", "), ss << ToString(std::get<I>(values))));
121 ss << ">";
122 }
123
124 static void Stringify(const std::tuple<T...>& v, std::ostringstream& ss) {
125 StringifyHelper(v, std::make_index_sequence<sizeof...(T)>(), ss);
126 }
127 };
128
129 } // namespace internal
130
131 // Converts any type to a string, preferring defined operator<<() or ToString()
132 // methods if they exist. If multiple `values` are given, returns the
133 // concatenation of the result of applying `ToString` to each value.
134 template <typename... Ts>
135 std::string ToString(const Ts&... values) {
136 std::ostringstream ss;
137 (...,
138 internal::ToStringHelper<std::remove_cvref_t<decltype(values)>>::Stringify(
139 values, ss));
140 return ss.str();
141 }
142
143 } // namespace base
144
145 #endif // BASE_STRINGS_TO_STRING_H_
146