|
12 | 12 | #include <stdint.h> // int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t
|
13 | 13 |
|
14 | 14 | #include <ios> // std::streamsize, forward declaration of std::istream // IWYU pragma: keep
|
15 |
| -#include <limits> // std::numeric_limits |
| 15 | +#include <cstddef> // std::size_t |
16 | 16 | #include <sstream> // std::istringstream // IWYU pragma: keep
|
17 | 17 | #include <string> // std::string
|
18 | 18 |
|
19 |
| -#ifdef KAITAI_STREAM_H_CPP11_SUPPORT |
20 |
| -#include <type_traits> // std::enable_if, std::is_integral |
21 |
| -#endif |
22 |
| - |
23 | 19 | namespace kaitai {
|
24 | 20 |
|
25 | 21 | /**
|
@@ -232,70 +228,72 @@ class kstream {
|
232 | 228 | */
|
233 | 229 | static int mod(int a, int b);
|
234 | 230 |
|
| 231 | + // NB: the following 6 overloads of `to_string` are exactly the ones that |
| 232 | + // [`std::to_string`](https://en.cppreference.com/w/cpp/string/basic_string/to_string) has. |
| 233 | + // Testing has shown that they are all necessary: if you remove any of them, you will get |
| 234 | + // something like `error: call to 'to_string' is ambiguous` when trying to call `to_string` |
| 235 | + // with the integer type for which you removed the overload. |
| 236 | + |
| 237 | + /** |
| 238 | + * Converts given integer `val` to a decimal string representation. |
| 239 | + * Should be used in place of `std::to_string(int)` (which is available only |
| 240 | + * since C++11) in older C++ implementations. |
| 241 | + */ |
| 242 | + static std::string to_string(int val) { |
| 243 | + return to_string_signed(val); |
| 244 | + } |
| 245 | + |
235 | 246 | /**
|
236 | 247 | * Converts given integer `val` to a decimal string representation.
|
237 |
| - * Should be used in place of std::to_string() (which is available only |
| 248 | + * Should be used in place of `std::to_string(long)` (which is available only |
238 | 249 | * since C++11) in older C++ implementations.
|
239 | 250 | */
|
240 |
| - template<typename I> |
| 251 | + static std::string to_string(long val) { |
| 252 | + return to_string_signed(val); |
| 253 | + } |
| 254 | + |
| 255 | +// The `long long` type is only available since C++11, so we use it only in C++11 mode. |
241 | 256 | #ifdef KAITAI_STREAM_H_CPP11_SUPPORT
|
242 |
| - // https://stackoverflow.com/a/27913885 |
243 |
| - typename std::enable_if< |
244 |
| - std::is_integral<I>::value && |
245 |
| - // check if we don't have something too large like GCC's `__int128_t` |
246 |
| - std::numeric_limits<I>::max() >= 0 && |
247 |
| - std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max(), |
248 |
| - std::string |
249 |
| - >::type |
250 |
| -#else |
251 |
| - std::string |
| 257 | + /** |
| 258 | + * Converts given integer `val` to a decimal string representation. |
| 259 | + * Should be used in place of `std::to_string(long long)` (which is available only |
| 260 | + * since C++11) in older C++ implementations. |
| 261 | + */ |
| 262 | + static std::string to_string(long long val) { |
| 263 | + return to_string_signed(val); |
| 264 | + } |
252 | 265 | #endif
|
253 |
| - static to_string(I val) { |
254 |
| - // in theory, `digits10 + 3` would be enough (minus sign + leading digit |
255 |
| - // + null terminator), but let's add a little more to be safe |
256 |
| - char buf[std::numeric_limits<I>::digits10 + 5]; |
257 |
| - if (val < 0) { |
258 |
| - buf[0] = '-'; |
259 |
| - |
260 |
| - // NB: `val` is negative and we need to get its absolute value (i.e. minus `val`). However, since |
261 |
| - // `int64_t` uses two's complement representation, its range is `[-2**63, 2**63 - 1] = |
262 |
| - // [-0x8000_0000_0000_0000, 0x7fff_ffff_ffff_ffff]` (both ends inclusive) and thus the naive |
263 |
| - // `-val` operation will overflow for `val = std::numeric_limits<int64_t>::min() = |
264 |
| - // -0x8000_0000_0000_0000` (because the result of `-val` is mathematically |
265 |
| - // `-(-0x8000_0000_0000_0000) = 0x8000_0000_0000_0000`, but the `int64_t` type can represent at |
266 |
| - // most `0x7fff_ffff_ffff_ffff`). And signed integer overflow is undefined behavior in C++. |
267 |
| - // |
268 |
| - // To avoid undefined behavior for `val = -0x8000_0000_0000_0000 = -2**63`, we do the following |
269 |
| - // steps for all negative `val`s: |
270 |
| - // |
271 |
| - // 1. Convert the signed (and negative) `val` to an unsigned `uint64_t` type. This is a |
272 |
| - // well-defined operation in C++: the resulting `uint64_t` value will be `val mod 2**64` (`mod` |
273 |
| - // is modulo). The maximum `val` we can have here is `-1` (because `val < 0`), a theoretical |
274 |
| - // minimum we are able to support would be `-2**64 + 1 = -0xffff_ffff_ffff_ffff` (even though |
275 |
| - // in practice the widest standard type is `int64_t` with the minimum of `-2**63`): |
276 |
| - // |
277 |
| - // * `static_cast<uint64_t>(-1) = -1 mod 2**64 = 2**64 + (-1) = 0xffff_ffff_ffff_ffff = 2**64 - 1` |
278 |
| - // * `static_cast<uint64_t>(-2**64 + 1) = (-2**64 + 1) mod 2**64 = 2**64 + (-2**64 + 1) = 1` |
279 |
| - // |
280 |
| - // 2. Subtract `static_cast<uint64_t>(val)` from `2**64 - 1 = 0xffff_ffff_ffff_ffff`. Since |
281 |
| - // `static_cast<uint64_t>(val)` is in range `[1, 2**64 - 1]` (see step 1), the result of this |
282 |
| - // subtraction will be mathematically in range `[0, (2**64 - 1) - 1] = [0, 2**64 - 2]`. So the |
283 |
| - // mathematical result cannot be negative, hence this unsigned integer subtraction can never |
284 |
| - // wrap around (which wouldn't be a good thing to rely upon because it confuses programmers and |
285 |
| - // code analysis tools). |
286 |
| - // |
287 |
| - // 3. Since we did mathematically `(2**64 - 1) - (2**64 + val) = -val - 1` so far (and we wanted |
288 |
| - // to do `-val`), we add `1` to correct that. From step 2 we know that the result of `-val - 1` |
289 |
| - // is in range `[0, 2**64 - 2]`, so adding `1` will not wrap (at most we could get `2**64 - 1 = |
290 |
| - // 0xffff_ffff_ffff_ffff`, which is still in the valid range of `uint64_t`). |
291 |
| - |
292 |
| - unsigned_to_decimal((std::numeric_limits<uint64_t>::max() - static_cast<uint64_t>(val)) + 1, &buf[1]); |
293 |
| - } else { |
294 |
| - unsigned_to_decimal(val, buf); |
295 |
| - } |
296 |
| - return std::string(buf); |
| 266 | + |
| 267 | + /** |
| 268 | + * Converts given integer `val` to a decimal string representation. |
| 269 | + * Should be used in place of `std::to_string(unsigned)` (which is available only |
| 270 | + * since C++11) in older C++ implementations. |
| 271 | + */ |
| 272 | + static std::string to_string(unsigned val) { |
| 273 | + return to_string_unsigned(val); |
| 274 | + } |
| 275 | + |
| 276 | + /** |
| 277 | + * Converts given integer `val` to a decimal string representation. |
| 278 | + * Should be used in place of `std::to_string(unsigned long)` (which is available only |
| 279 | + * since C++11) in older C++ implementations. |
| 280 | + */ |
| 281 | + static std::string to_string(unsigned long val) { |
| 282 | + return to_string_unsigned(val); |
297 | 283 | }
|
298 | 284 |
|
| 285 | +// The `unsigned long long` type is only available since C++11, so we use it only in C++11 mode. |
| 286 | +#ifdef KAITAI_STREAM_H_CPP11_SUPPORT |
| 287 | + /** |
| 288 | + * Converts given integer `val` to a decimal string representation. |
| 289 | + * Should be used in place of `std::to_string(unsigned long long)` (which is available only |
| 290 | + * since C++11) in older C++ implementations. |
| 291 | + */ |
| 292 | + static std::string to_string(unsigned long long val) { |
| 293 | + return to_string_unsigned(val); |
| 294 | + } |
| 295 | +#endif |
| 296 | + |
299 | 297 | /**
|
300 | 298 | * Converts string `str` to an integer value. Throws an exception if the
|
301 | 299 | * string is not a valid integer.
|
@@ -347,7 +345,9 @@ class kstream {
|
347 | 345 | void init();
|
348 | 346 | void exceptions_enable() const;
|
349 | 347 |
|
350 |
| - static void unsigned_to_decimal(uint64_t number, char *buffer); |
| 348 | + static void unsigned_to_decimal(uint64_t number, char *buf, std::size_t &buf_contents_start); |
| 349 | + static std::string to_string_signed(int64_t val); |
| 350 | + static std::string to_string_unsigned(uint64_t val); |
351 | 351 |
|
352 | 352 | #ifdef KS_STR_ENCODING_WIN32API
|
353 | 353 | enum {
|
|
0 commit comments