From 37b7cd171543152a7569bfa63b6786ec21198f40 Mon Sep 17 00:00:00 2001 From: clyne Date: Fri, 26 Jun 2020 16:41:17 -0400 Subject: [PATCH 01/10] Initialize buf for C++17 GCC; make to_string constexpr https://www.reddit.com/r/cpp/comments/hgcaih/compiletime_integertostring_conversion_c17/fw3d2fm/ --- to_string.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 116cdd8..15df021 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -25,12 +25,12 @@ struct to_string_t { unsigned int len = N >= 0 ? 1 : 2; for (auto n = N < 0 ? -N : N; n; len++, n /= base); return len; - }())]; + }())] = {}; /** * Constructs the object, filling `buf` with the string representation of N. */ - constexpr to_string_t() { + constexpr to_string_t() noexcept { auto ptr = buf + sizeof(buf) / sizeof(buf[0]); *--ptr = '\0'; for (auto n = N < 0 ? -N : N; n; n /= base) @@ -58,6 +58,6 @@ struct to_string_t { * Simplifies use of `to_string_t` from `to_string_t()` to `to_string`. */ template -to_string_t to_string; +constexpr to_string_t to_string; #endif // TCSULLIVAN_TO_STRING_HPP_ From 4cffc3ffbc96df9fbaabf67bf82652340b6af856 Mon Sep 17 00:00:00 2001 From: clyne Date: Fri, 26 Jun 2020 16:43:20 -0400 Subject: [PATCH 02/10] Remove resolved issue with GCC/C++17 --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 9f9efe1..ce688fe 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ * Convert any integral type to a string at compile-time * Supports converting to any base between 2 and 36 inclusive * No external dependencies, only includes `type_traits` for template parameter checking -* Works best in C++20 GCC or C++17/20 Clang **How to use:** @@ -22,10 +21,6 @@ With `to_string`, all that will be found in program disassembly are the resultin Try it [on Compiler Explorer](https://godbolt.org/z/T-MFoh). -**Known issues:** - -* With C++17 GCC, `to_string` must be used to initialize variables; otherwise, the integer-string conversion is done at run-time. - # How it works The basic structure of `to_string` is shown below: From 1d8ef0361c0636976f5fd835ef20101385f28de3 Mon Sep 17 00:00:00 2001 From: clyne Date: Fri, 26 Jun 2020 17:47:48 -0400 Subject: [PATCH 03/10] Forgot to support to_string<0> --- to_string.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 15df021..445c97f 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -22,7 +22,7 @@ struct to_string_t { // The lambda calculates what the string length of N will be, so that `buf` // fits to the number perfectly. char buf[([] { - unsigned int len = N >= 0 ? 1 : 2; + unsigned int len = N > 0 ? 1 : 2; for (auto n = N < 0 ? -N : N; n; len++, n /= base); return len; }())] = {}; @@ -31,12 +31,16 @@ struct to_string_t { * Constructs the object, filling `buf` with the string representation of N. */ constexpr to_string_t() noexcept { - auto ptr = buf + sizeof(buf) / sizeof(buf[0]); - *--ptr = '\0'; - for (auto n = N < 0 ? -N : N; n; n /= base) - *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[n % base]; - if (N < 0) - *--ptr = '-'; + if (N != 0) { + auto ptr = buf + sizeof(buf) / sizeof(buf[0]); + *--ptr = '\0'; + for (auto n = N < 0 ? -N : N; n; n /= base) + *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[n % base]; + if (N < 0) + *--ptr = '-'; + } else { + buf[0] = '0'; + } } /** From 8dfd9e49ad94f1e1d95deb16fcd2a794c7a7c72f Mon Sep 17 00:00:00 2001 From: clyne Date: Fri, 26 Jun 2020 18:05:11 -0400 Subject: [PATCH 04/10] Add support for different character widths --- to_string.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 445c97f..21b5f48 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -15,17 +15,17 @@ * @tparam N Number to convert * @tparam base Desired base, can be from 2 to 36 */ -template, int> = 0, std::enable_if_t<(base > 1 && base < 37), int> = 0> struct to_string_t { // The lambda calculates what the string length of N will be, so that `buf` // fits to the number perfectly. - char buf[([] { - unsigned int len = N > 0 ? 1 : 2; - for (auto n = N < 0 ? -N : N; n; len++, n /= base); - return len; - }())] = {}; + char_type buf[([] { + unsigned int len = N > 0 ? 1 : 2; + for (auto n = N < 0 ? -N : N; n; len++, n /= base); + return len; + }())] = {}; /** * Constructs the object, filling `buf` with the string representation of N. @@ -46,14 +46,14 @@ struct to_string_t { /** * Allows implicit conversion of this object to a `char *`. */ - constexpr operator char *() { + constexpr operator char_type *() { return buf; } /** * Allows implicit conversion of this object to a `const char *`. */ - constexpr operator const char *() const { + constexpr operator const char_type *() const { return buf; } }; @@ -61,7 +61,7 @@ struct to_string_t { /** * Simplifies use of `to_string_t` from `to_string_t()` to `to_string`. */ -template -constexpr to_string_t to_string; +template +constexpr to_string_t to_string; #endif // TCSULLIVAN_TO_STRING_HPP_ From 84401c74731927a237ccd8e7b59444d3f0630664 Mon Sep 17 00:00:00 2001 From: clyne Date: Fri, 26 Jun 2020 18:11:57 -0400 Subject: [PATCH 05/10] Add support for range-based for loops --- to_string.hpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 21b5f48..1d88b1a 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -43,19 +43,15 @@ struct to_string_t { } } - /** - * Allows implicit conversion of this object to a `char *`. - */ - constexpr operator char_type *() { - return buf; - } + // Support implicit casting to `char *` or `const char *`. + constexpr operator char_type *() { return buf; } + constexpr operator const char_type *() const { return buf; } - /** - * Allows implicit conversion of this object to a `const char *`. - */ - constexpr operator const char_type *() const { - return buf; - } + // Support range-based for loops + constexpr auto begin() { return buf; } + constexpr auto begin() const { return buf; } + constexpr auto end() { return buf + sizeof(buf) / sizeof(buf[0]); } + constexpr auto end() const { return buf + sizeof(buf) / sizeof(buf[0]); } }; /** From f26a6dc4d527d0ae6d51ae61701a7e908e7fdb28 Mon Sep 17 00:00:00 2001 From: clyne Date: Fri, 26 Jun 2020 18:50:47 -0400 Subject: [PATCH 06/10] Add note on custom character types --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ce688fe..aa99175 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ * Convert any integral type to a string at compile-time * Supports converting to any base between 2 and 36 inclusive * No external dependencies, only includes `type_traits` for template parameter checking +* Supports custom character types, e.g. `to_string<123, 10, wchar_t>` **How to use:** From 8b2e3c7bc5e6f66363c6256f5f7eb57e366d5b16 Mon Sep 17 00:00:00 2001 From: clyne Date: Fri, 26 Jun 2020 18:56:13 -0400 Subject: [PATCH 07/10] Update "how it works" section --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index aa99175..f2c55e1 100644 --- a/README.md +++ b/README.md @@ -27,22 +27,26 @@ Try it [on Compiler Explorer](https://godbolt.org/z/T-MFoh). The basic structure of `to_string` is shown below: ```cpp -template +template struct to_string_t { - char buf[]; // Size selection explained later. - constexpr to_string_t() {} // Converts the integer to a string stored in buf. - constexpr operator char *() {} // These allow for the object to be implicitly converted - constexpr operator const char *() {} // to a character pointer. + char_type buf[]; // Size selection explained later. + constexpr to_string_t() {} // Converts the integer to a string stored in buf. + constexpr operator char_type *() {} // These allow for the object to be implicitly converted + constexpr operator const char_type *() {} // to a character pointer. + + // begin() and end() are supported too. }; -template -to_string_t to_string; // Simplifies usage: to_string_t() becomes to_string. +template +constexpr to_string_t to_string; // Simplifies usage, e.g. to_string_t<367>() becomes to_string<367>. ``` Since the number and base are template parameters, each differing `to_string` use will get its own character buffer. The integer/string conversion is done using a simple method I learned over the years, where the string is built in reverse using `n % base` to calculate the value of the lowest digit: +(*Note: The below examples of code are not up-to-date, though they still give a general idea of how `to_string` works.*) + ```cpp constexpr to_string_t() { auto ptr = buf + sizeof(buf) / sizeof(buf[0]); From 9aa286ebc05d36f7ce4b4c840ac43904a8cc7ade Mon Sep 17 00:00:00 2001 From: neargye Date: Sat, 27 Jun 2020 21:35:10 +0500 Subject: [PATCH 08/10] container-like --- to_string.hpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 1d88b1a..b9e0cce 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -18,15 +18,16 @@ template, int> = 0, std::enable_if_t<(base > 1 && base < 37), int> = 0> -struct to_string_t { +class to_string_t { // The lambda calculates what the string length of N will be, so that `buf` // fits to the number perfectly. - char_type buf[([] { + char_type buf[([]() constexpr noexcept { unsigned int len = N > 0 ? 1 : 2; for (auto n = N < 0 ? -N : N; n; len++, n /= base); return len; }())] = {}; + public: /** * Constructs the object, filling `buf` with the string representation of N. */ @@ -44,14 +45,24 @@ struct to_string_t { } // Support implicit casting to `char *` or `const char *`. - constexpr operator char_type *() { return buf; } - constexpr operator const char_type *() const { return buf; } - - // Support range-based for loops - constexpr auto begin() { return buf; } - constexpr auto begin() const { return buf; } - constexpr auto end() { return buf + sizeof(buf) / sizeof(buf[0]); } - constexpr auto end() const { return buf + sizeof(buf) / sizeof(buf[0]); } + constexpr operator char_type *() noexcept { return buf; } + constexpr operator const char_type *() const noexcept { return buf; } + + constexpr auto size() const noexcept { return sizeof(buf) / sizeof(buf[0]); } + // Element access + constexpr auto data() noexcept { return buf; } + constexpr auto data() const noexcept { return buf; } + constexpr auto operator[](unsigned int i) noexcept { return buf[i]; } + constexpr auto operator[](unsigned int i) const noexcept { return buf[i]; } + constexpr auto front() noexcept { return buf[0]; } + constexpr auto front() const noexcept { return buf[0]; } + constexpr auto back() noexcept { return buf[size() - 1]; } + constexpr auto back() const noexcept { return buf[size() - 1]; } + // Iterators + constexpr auto begin() noexcept { return buf; } + constexpr auto begin() const noexcept { return buf; } + constexpr auto end() noexcept { return buf + size(); } + constexpr auto end() const noexcept { return buf + size(); } }; /** @@ -59,5 +70,5 @@ struct to_string_t { */ template constexpr to_string_t to_string; - + #endif // TCSULLIVAN_TO_STRING_HPP_ From e395be3782f7adf92dbdd1e7438ea038eebecdf3 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 27 Jun 2020 18:31:53 -0400 Subject: [PATCH 09/10] Use end() in constructor --- to_string.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index b9e0cce..11e5906 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -32,9 +32,9 @@ class to_string_t { * Constructs the object, filling `buf` with the string representation of N. */ constexpr to_string_t() noexcept { + auto ptr = end(); + *--ptr = '\0'; if (N != 0) { - auto ptr = buf + sizeof(buf) / sizeof(buf[0]); - *--ptr = '\0'; for (auto n = N < 0 ? -N : N; n; n /= base) *--ptr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[n % base]; if (N < 0) From 6ae484c8011dc5f10b552932dd3b977b46b4a1f5 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 27 Jun 2020 18:37:33 -0400 Subject: [PATCH 10/10] add const and references to container operations --- to_string.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/to_string.hpp b/to_string.hpp index 11e5906..3f04272 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -51,18 +51,18 @@ class to_string_t { constexpr auto size() const noexcept { return sizeof(buf) / sizeof(buf[0]); } // Element access constexpr auto data() noexcept { return buf; } - constexpr auto data() const noexcept { return buf; } - constexpr auto operator[](unsigned int i) noexcept { return buf[i]; } - constexpr auto operator[](unsigned int i) const noexcept { return buf[i]; } - constexpr auto front() noexcept { return buf[0]; } - constexpr auto front() const noexcept { return buf[0]; } - constexpr auto back() noexcept { return buf[size() - 1]; } - constexpr auto back() const noexcept { return buf[size() - 1]; } + constexpr const auto data() const noexcept { return buf; } + constexpr auto& operator[](unsigned int i) noexcept { return buf[i]; } + constexpr const auto& operator[](unsigned int i) const noexcept { return buf[i]; } + constexpr auto& front() noexcept { return buf[0]; } + constexpr const auto& front() const noexcept { return buf[0]; } + constexpr auto& back() noexcept { return buf[size() - 1]; } + constexpr const auto& back() const noexcept { return buf[size() - 1]; } // Iterators constexpr auto begin() noexcept { return buf; } - constexpr auto begin() const noexcept { return buf; } + constexpr const auto begin() const noexcept { return buf; } constexpr auto end() noexcept { return buf + size(); } - constexpr auto end() const noexcept { return buf + size(); } + constexpr const auto end() const noexcept { return buf + size(); } }; /**