13

I'm looking for a way to convert a number to a string literal at compile time. It should look something like this:

template <unsigned num>
struct num_to_string {
    constexpr static char value[] = /* ... magic goes here ... */;
};

So that num_to_string<5>::value is equal to "5" or {'5', '\0'}.

This can be useful for generating a string at compile time from a number that is the result of some other constexpr number calculations.

Also note that I'm only interested in unsigned numbers since that should be easier to deal with. Bonus points for the signed version :)

EDIT: Note that this is similar to C++ convert integer to string at compile time, but not the same. Here I explicitly want something that uses constexpr and not macros to aid in generic programming.

Community
  • 1
  • 1
syvex
  • 7,518
  • 9
  • 43
  • 47

1 Answers1

38

Variadic templates to the rescue. :)

namespace detail
{
    template<unsigned... digits>
    struct to_chars { static const char value[]; };

    template<unsigned... digits>
    constexpr char to_chars<digits...>::value[] = {('0' + digits)..., 0};

    template<unsigned rem, unsigned... digits>
    struct explode : explode<rem / 10, rem % 10, digits...> {};

    template<unsigned... digits>
    struct explode<0, digits...> : to_chars<digits...> {};
}

template<unsigned num>
struct num_to_string : detail::explode<num> {};

As always, here's a live example on Coliru showing usage and the (relevant) generated assembly.


It's straightforward to adapt this approach to support negative numbers as well. Here's a more generic form that requires the user to input the integer's type:

namespace detail
{
    template<uint8_t... digits> struct positive_to_chars { static const char value[]; };
    template<uint8_t... digits> constexpr char positive_to_chars<digits...>::value[] = {('0' + digits)..., 0};

    template<uint8_t... digits> struct negative_to_chars { static const char value[]; };
    template<uint8_t... digits> constexpr char negative_to_chars<digits...>::value[] = {'-', ('0' + digits)..., 0};

    template<bool neg, uint8_t... digits>
    struct to_chars : positive_to_chars<digits...> {};

    template<uint8_t... digits>
    struct to_chars<true, digits...> : negative_to_chars<digits...> {};

    template<bool neg, uintmax_t rem, uint8_t... digits>
    struct explode : explode<neg, rem / 10, rem % 10, digits...> {};

    template<bool neg, uint8_t... digits>
    struct explode<neg, 0, digits...> : to_chars<neg, digits...> {};

    template<typename T>
    constexpr uintmax_t cabs(T num) { return (num < 0) ? -num : num; }
}

template<typename Integer, Integer num>
struct string_from : detail::explode<(num < 0), detail::cabs(num)> {};

Its usage is like:

string_from<signed, -1>::value

as demonstrated in the live example on Coliru.

Maël Nison
  • 7,055
  • 7
  • 46
  • 77
tclamb
  • 1,469
  • 13
  • 17
  • I really like this solution, there is only one point I'd like to mention: It is not really `constexpr`. I realized that when I wanted to use it and tested it with `static_assert()`. It's no big deal to make it `constexpr`, though: Define the `value` arrays as `constexpr` and initialize them in the declaration (necessary). – Rene Dec 31 '16 at 12:14
  • Why 1st `template` and second `template` have different types, shouldn't they be same? – kyb Dec 29 '17 at 10:10
  • 1
    Very nice. `positive_to_chars` needs an additional specialization to handle the case when the number is exactly zero. Result should be "0", not empty string. – Christopher Bruns Oct 28 '19 at 20:19
  • Is this still the preferred way to do this? – Moberg Dec 08 '20 at 16:15
  • Thank you for this finding. The variadic template is beautiful. An exquisite application of recursion and declarative programming! – D-FENS Dec 06 '21 at 11:00