xref: /aosp_15_r20/external/pigweed/pw_string/public/pw_string/to_string.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2019 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 // Provides the ToString function, which outputs string representations of
17 // arbitrary types to a buffer.
18 //
19 // ToString returns the number of characters written, excluding the null
20 // terminator, and a status. A null terminator is always written if the output
21 // buffer has room.
22 //
23 // ToString functions may be defined for any type. This is done by providing a
24 // ToString template specialization in the pw namespace. The specialization must
25 // follow ToString's semantics:
26 //
27 //   1. Always null terminate if the output buffer has room.
28 //   2. Return the number of characters written, excluding the null terminator,
29 //      as a StatusWithSize.
30 //   3. If the buffer is too small to fit the output, return a StatusWithSize
31 //      with the number of characters written and a status of
32 //      RESOURCE_EXHAUSTED. Other status codes may be used for different errors.
33 //
34 // For example, providing the following specialization would allow ToString, and
35 // any classes that use it, to print instances of a custom type:
36 //
37 //   namespace pw {
38 //
39 //   template <>
40 //   StatusWithSize ToString<SomeCustomType>(const SomeCustomType& value,
41 //                           span<char> buffer) {
42 //     return /* ... implementation ... */;
43 //   }
44 //
45 //   }  // namespace pw
46 //
47 // Note that none of the functions in this module use std::snprintf. ToString
48 // overloads may use snprintf if needed, but the ToString semantics must be
49 // maintained.
50 //
51 // ToString is a low-level function. To write complex objects to string, a
52 // StringBuilder may be easier to work with. StringBuilder's operator<< may be
53 // overloaded for custom types.
54 
55 #include <chrono>
56 #include <optional>
57 #include <string_view>
58 #include <type_traits>
59 
60 #include "pw_result/result.h"
61 #include "pw_span/span.h"
62 #include "pw_status/status.h"
63 #include "pw_status/status_with_size.h"
64 #include "pw_string/format.h"
65 #include "pw_string/internal/config.h"
66 #include "pw_string/type_to_string.h"
67 
68 namespace pw {
69 
70 template <typename T>
71 StatusWithSize ToString(const T& value, span<char> buffer);
72 
73 namespace internal {
74 
75 template <typename T>
76 struct is_std_optional : std::false_type {};
77 
78 template <typename T>
79 struct is_std_optional<std::optional<T>> : std::true_type {};
80 
81 template <typename T>
82 constexpr bool is_std_optional_v = is_std_optional<T>::value;
83 
84 template <typename, typename = void>
85 constexpr bool is_iterable_v = false;
86 
87 template <typename T>
88 constexpr bool is_iterable_v<T,
89                              std::void_t<decltype(std::declval<T>().begin()),
90                                          decltype(std::declval<T>().end())>> =
91     true;
92 
93 template <typename BeginType, typename EndType>
94 inline StatusWithSize IterableToString(BeginType begin,
95                                        EndType end,
96                                        span<char> buffer) {
97   StatusWithSize s;
98   s.UpdateAndAdd(ToString("[", buffer));
99   auto iter = begin;
100   if (iter != end && s.ok()) {
101     s.UpdateAndAdd(ToString(*iter, buffer.subspan(s.size())));
102     ++iter;
103   }
104   while (iter != end && s.ok()) {
105     s.UpdateAndAdd(ToString(", ", buffer.subspan(s.size())));
106     s.UpdateAndAdd(ToString(*iter, buffer.subspan(s.size())));
107     ++iter;
108   }
109   s.UpdateAndAdd(ToString("]", buffer.subspan(s.size())));
110   s.ZeroIfNotOk();
111   return s;
112 }
113 
114 /// A shared duration type represented using int64_t nanoseconds.
115 using UnifiedDuration = std::chrono::duration<int64_t, std::nano>;
116 
117 // Whether or not an std::chrono::duration<Rep, Period> type can be converted
118 // to `std::chrono::duration<int64_t, std::nano>`.
119 template <typename T, typename = void>
120 constexpr bool is_unifyable_duration_v = false;
121 
122 template <typename T>
123 constexpr bool is_unifyable_duration_v<
124     T,
125     std::void_t<decltype(std::chrono::round<UnifiedDuration>(
126         std::declval<T>()))>> = true;
127 
128 template <typename Rep,
129           typename Period,
130           typename = std::enable_if_t<internal::is_unifyable_duration_v<
131               std::chrono::duration<Rep, Period>>>>
132 inline StatusWithSize DurationToString(
133     const std::chrono::duration<Rep, Period>& duration, span<char> buffer) {
134   // Cast to SourceDuration type before comparison, as naiive comparison
135   // may result in casts the other direction or from float -> int which
136   // cause undefined behavior.
137   using SourceDuration = std::chrono::duration<Rep, Period>;
138 
139   // std::chrono::years is not available until C++20.
140   using Years = std::chrono::duration<int64_t, std::ratio<31556952>>;
141 
142   // std::chrono specifies support for at least +/-292 years. Beyond that,
143   // the chrono and duration APIs do not take care to avoid overflow, so
144   // we must short-circuit.
145   if (duration < std::chrono::ceil<SourceDuration>(Years(-292))) {
146     return ToString("An unrepresentably long time ago (>292 years).", buffer);
147   }
148   if (duration > std::chrono::floor<SourceDuration>(Years(292))) {
149     return ToString("An unrepresentably long time in the future (>292 years).",
150                     buffer);
151   }
152 
153   // This is an approximation which unfortunately can result in some errors
154   // reporting things like `21321839210 != 21321839210` (both values different,
155   // but rounded to the same). Unfortunately there isn't an "easy" alternative:
156   // duration_cast here can result in UB (even with only integer reps!) due to
157   // an unchecked multiplication by the ratio's numerator before the division
158   // by the denominator.
159   auto nanos = std::chrono::round<UnifiedDuration>(duration);
160   StatusWithSize s;
161   s.UpdateAndAdd(ToString(nanos.count(), buffer));
162   s.UpdateAndAdd(ToString("ns", buffer.subspan(s.size())));
163   s.ZeroIfNotOk();
164   return s;
165 }
166 
167 }  // namespace internal
168 
169 // This function provides string printing numeric types, enums, and anything
170 // that convertible to a std::string_view, such as std::string.
171 template <typename T>
172 StatusWithSize ToString(const T& value, span<char> buffer) {
173   if constexpr (std::is_same_v<std::remove_cv_t<T>, bool>) {
174     return string::BoolToString(value, buffer);
175   } else if constexpr (std::is_same_v<std::remove_cv_t<T>, char>) {
176     return string::Copy(std::string_view(&value, 1), buffer);
177   } else if constexpr (std::is_integral_v<T>) {
178     return string::IntToString(value, buffer);
179   } else if constexpr (std::is_enum_v<T>) {
180     return string::IntToString(std::underlying_type_t<T>(value), buffer);
181   } else if constexpr (std::is_floating_point_v<T>) {
182     if constexpr (string::internal::config::kEnableDecimalFloatExpansion) {
183       // TODO(hepler): Look into using the float overload of std::to_chars when
184       // it is available.
185       return string::Format(buffer, "%.3f", value);
186     } else {
187       return string::FloatAsIntToString(static_cast<float>(value), buffer);
188     }
189   } else if constexpr (std::is_convertible_v<T, std::string_view>) {
190     return string::CopyStringOrNull(value, buffer);
191   } else if constexpr (std::is_pointer_v<std::remove_cv_t<T>> ||
192                        std::is_null_pointer_v<T>) {
193     return string::PointerToString(value, buffer);
194   } else if constexpr (internal::is_std_optional_v<std::remove_cv_t<T>>) {
195     if (value.has_value()) {
196       // NOTE: `*value`'s `ToString` is not wrapped for simplicity in the
197       // output.
198       //
199       // This is simpler, but may cause confusion in the rare case that folks
200       // are comparing nested optionals. For example,
201       // std::optional(std::nullopt) != std::nullopt will display as
202       // `std::nullopt != std::nullopt`.
203       return ToString(*value, buffer);
204     } else {
205       return ToString(std::nullopt, buffer);
206     }
207   } else if constexpr (std::is_same_v<std::remove_cv_t<T>, std::nullopt_t>) {
208     return string::CopyStringOrNull("std::nullopt", buffer);
209   } else if constexpr (internal::is_iterable_v<T>) {
210     return internal::IterableToString(value.begin(), value.end(), buffer);
211   } else {
212     // By default, no definition of UnknownTypeToString is provided.
213     return string::UnknownTypeToString(value, buffer);
214   }
215 }
216 
217 // ToString overloads for Pigweed types. To override ToString for a custom type,
218 // specialize the ToString template function.
219 inline StatusWithSize ToString(Status status, span<char> buffer) {
220   return string::Copy(status.str(), buffer);
221 }
222 
223 inline StatusWithSize ToString(pw_Status status, span<char> buffer) {
224   return ToString(Status(status), buffer);
225 }
226 
227 template <typename T>
228 inline StatusWithSize ToString(const Result<T>& result, span<char> buffer) {
229   if (result.ok()) {
230     StatusWithSize s;
231     s.UpdateAndAdd(ToString("Ok(", buffer));
232     s.UpdateAndAdd(ToString(*result, buffer.subspan(s.size())));
233     s.UpdateAndAdd(ToString(")", buffer.subspan(s.size())));
234     s.ZeroIfNotOk();
235     return s;
236   }
237   return ToString(result.status(), buffer);
238 }
239 
240 inline StatusWithSize ToString(std::byte byte, span<char> buffer) {
241   return string::IntToHexString(static_cast<unsigned>(byte), buffer, 2);
242 }
243 
244 template <
245     typename Clock,
246     typename Duration,
247     typename = std::enable_if_t<internal::is_unifyable_duration_v<Duration>>>
248 inline StatusWithSize ToString(
249     const std::chrono::time_point<Clock, Duration>& time_point,
250     span<char> buffer) {
251   return internal::DurationToString(time_point.time_since_epoch(), buffer);
252 }
253 
254 template <typename Rep,
255           typename Period,
256           typename = std::enable_if_t<internal::is_unifyable_duration_v<
257               std::chrono::duration<Rep, Period>>>>
258 inline StatusWithSize ToString(
259     const std::chrono::duration<Rep, Period>& duration, span<char> buffer) {
260   return internal::DurationToString(duration, buffer);
261 }
262 
263 }  // namespace pw
264