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