C++ convert integer to string at compile time
Asked Answered
B

6

25

I want to do something like this:

template<int N>
char* foo() {
  // return a compile-time string containing N, equivalent to doing
  // ostringstream ostr; 
  // ostr << N;
  // return ostr.str().c_str();
}

It seems like the boost MPL library might allow this but I couldn't really figure out how to use it to accomplish this. Is this possible?

Burtie answered 15/7, 2011 at 21:37 Comment(6)
Does it need to be a function like that? You can do it more easily with a pre-processor macro (you'd only lose type-safety).Speculate
itoa() function will do the trick. cplusplus.com/reference/clibrary/cstdlib/itoaGout
Define a compile time string... std::string is a run-time one. You can do semi-runtime magic, but not pure compile-time. Preprocessor is your best best.Irwinirwinn
@Ram: The OP wants the conversion during compile time. The itoa function works during run-time.Lenitalenitive
To clarify, getting a const char* would be fine.Burtie
@vanza, how would I do it with a macro?Burtie
A
17

This can be done using C++14 without any external dependencies. The key addition to the standard is the ability to have non-trivial constexpr constructors, allowing the functionality to be contained within a simple class.

Given an integer template parameter, the constructor can carry out the integer-to-string conversion. This is stored in a member character buffer whose size is determined by an additional constexpr function. Then, a user-defined conversion provides access to the buffer:

#include <cstdint>

template<std::intmax_t N>
class to_string_t {

    constexpr static auto buflen() noexcept {
        unsigned int len = N > 0 ? 1 : 2;
        for (auto n = N; n; len++, n /= 10);
        return len;
    }

    char buf[buflen()] = {};

public:
    constexpr to_string_t() noexcept {
        auto ptr = buf + buflen();
        *--ptr = '\0';

        if (N != 0) {
            for (auto n = N; n; n /= 10)
                *--ptr = "0123456789"[(N < 0 ? -1 : 1) * (n % 10)];
            if (N < 0)
                *--ptr = '-';
        } else {
            buf[0] = '0';
        }
    }

    constexpr operator const char *() const { return buf; }
};

Finally, a variable template (another C++14 addition) simplifies the syntax:

template<std::intmax_t N>
constexpr to_string_t<N> to_string;

puts(to_string<62017>); // prints "62017"

The functionality can be extended to support other bases (e.g. hexadecimal), wide character types, and the common container interface; I've packed this all into a single header and put it on GitHub at tcsullivan/constexpr-to-string.

With C++20, this can also be extended to support floating-point numbers. A container type is needed for the floating-point literal, which previously could not be a template parameter. See the f_to_string.hpp header in the GitHub repo for the implementation.

Auston answered 26/6, 2020 at 14:3 Comment(4)
Excellent! That works great. The only small thing is to be able to choose between upper and lower for hexadecimal.Ensile
Switched the accepted answer to this one since it's more useful in modern C++Burtie
This is effectively a link-only answer to your code on Github. Nothing in this answer shows how the conversion code actually works, or what C++17 features it uses. (Presumably extensions to what's allowed with constexpr.) It would be nice to at least show a minimal version in the answer itself.Tantalite
@PeterCordes You're right, this needed a real explanation. I've updated my answer to be more thorough, and discovered that a C++14 implementation (now shown) is possible.Auston
W
30

First of all, if usually you know the number at run time, you can as easily build the same string. That is, if you have 12 in your program, you can have also "12".

Preprocessor macros can add also quotes to arguments, so you can write:

#define STRINGIFICATOR(X) #X

This, whenever you write STRINGIFICATOR(2), it will produce "2".

However, it actually can be done without macros (using compile-time metaprogramming). It is not straightforward, so I cannot give the exact code, but I can give you ideas on how to do it:

  1. Write a recursive template using the number to be converted. The template will recurse till the base case, that is, the number is less than 10.
  2. In each iteration, you can have the N%10 digit to be converted into a character as T.E.D. suggests, and using mpl::string to build the compile-time string that appends that character.
  3. You'll end up building a mpl::string, that has a static value() string.

I took the time to implement it as a personal exercise. Not bad at the end:

#include <iostream>
#include <boost/mpl/string.hpp>

using namespace boost;

// Recursive case
template <bool b, unsigned N>
struct int_to_string2
{
        typedef typename mpl::push_back<
                typename int_to_string2< N < 10, N/10>::type
                                         , mpl::char_<'0' + N%10>
                                         >::type type;
};

// Base case
template <>
struct int_to_string2<true,0>
{
        typedef mpl::string<> type;
};


template <unsigned N>
struct int_to_string
{
        typedef typename mpl::c_str<typename int_to_string2< N < 10 , N>::type>::type type;
};

int
main (void)
{
        std::cout << int_to_string<1099>::type::value << std::endl;
        return 0;
}
Wergild answered 15/7, 2011 at 22:14 Comment(8)
cool, I was just working on this but didn't know how to concatenate the chars :)Defamation
@yi_H: mpl provides the type push_front<string_type,char>::type to define a compile-time string that has char prepended to an existing string.Wergild
@Diego : Your code doesn't appear to handle negative values. Also, for those curious, I wrote the reverse of this code (compile time string-to-integral using mpl::string) some time ago; the code can be found in this answer.Coparcener
@ildjam: Yes, it does not cover negative numbers. I just did this in spare time just to practice. It won't be too difficult to add negative number support, AFAIK.Wergild
This is pretty awesome. I shudder to think how much large numbers would inflate compile time... unless I'm missing something.Usual
The compile time is in the order of the number of digits, so even a 64 digit number (rather immense) would take just 64*x, where x is the time of a one-digit number. Not that much time, I think.Wergild
I appreciate the solution but gee, so much code to do so little.Groupie
This, whenever you write STRINGIFICATOR(2), it will produce "2". Well, not exactly. If you try to print DBL_MIN you might get something like double(2.22507385850720138309023271733240406e-308L) and (-0x7fffffff - 1) for INT_MIN. The macro is a nice shortcut, but it exposes limit macros implementation details.Endearment
U
22

I know this question is a few years old now, but I wanted a solution using pure C++11, with no boost dependency. So here is some code (with ideas borrowed from this answer to a different question):

/* IMPLEMENTATION */

/* calculate absolute value */
constexpr int abs_val (int x)
    { return x < 0 ? -x : x; }

/* calculate number of digits needed, including minus sign */
constexpr int num_digits (int x)
    { return x < 0 ? 1 + num_digits (-x) : x < 10 ? 1 : 1 + num_digits (x / 10); }

/* metaprogramming string type: each different string is a unique type */
template<char... args>
struct metastring {
    const char data[sizeof... (args)] = {args...};
};

/* recursive number-printing template, general case (for three or more digits) */
template<int size, int x, char... args>
struct numeric_builder {
    typedef typename numeric_builder<size - 1, x / 10, '0' + abs_val (x) % 10, args...>::type type;
};

/* special case for two digits; minus sign is handled here */
template<int x, char... args>
struct numeric_builder<2, x, args...> {
    typedef metastring<x < 0 ? '-' : '0' + x / 10, '0' + abs_val (x) % 10, args...> type;
};

/* special case for one digit (positive numbers only) */
template<int x, char... args>
struct numeric_builder<1, x, args...> {
    typedef metastring<'0' + x, args...> type;
};

/* convenience wrapper for numeric_builder */
template<int x>
class numeric_string
{
private:
    /* generate a unique string type representing this number */
    typedef typename numeric_builder<num_digits (x), x, '\0'>::type type;

    /* declare a static string of that type (instantiated later at file scope) */
    static constexpr type value {};

public:
    /* returns a pointer to the instantiated string */
    static constexpr const char * get ()
        { return value.data; }
};

/* instantiate numeric_string::value as needed for different numbers */
template<int x>
constexpr typename numeric_string<x>::type numeric_string<x>::value;

/* SAMPLE USAGE */

#include <stdio.h>

/* exponentiate a number, just for fun */
static constexpr int exponent (int x, int e)
    { return e ? x * exponent (x, e - 1) : 1; }

/* test a few sample numbers */
static constexpr const char * five = numeric_string<5>::get ();
static constexpr const char * one_ten = numeric_string<110>::get ();
static constexpr const char * minus_thirty = numeric_string<-30>::get ();

/* works for any constant integer, including constexpr calculations */
static constexpr const char * eight_cubed = numeric_string<exponent (8, 3)>::get ();

int main (void)
{
    printf ("five = %s\n", five);
    printf ("one ten = %s\n", one_ten);
    printf ("minus thirty = %s\n", minus_thirty);
    printf ("eight cubed = %s\n", eight_cubed);

    return 0;
}

Output:

five = 5
one ten = 110
minus thirty = -30
eight cubed = 512
Unintelligent answered 9/11, 2014 at 4:21 Comment(1)
How to call this from loop in constexpr function? numeric_builder<i> and my compiler says argument is not constant expression?Coinsure
A
17

This can be done using C++14 without any external dependencies. The key addition to the standard is the ability to have non-trivial constexpr constructors, allowing the functionality to be contained within a simple class.

Given an integer template parameter, the constructor can carry out the integer-to-string conversion. This is stored in a member character buffer whose size is determined by an additional constexpr function. Then, a user-defined conversion provides access to the buffer:

#include <cstdint>

template<std::intmax_t N>
class to_string_t {

    constexpr static auto buflen() noexcept {
        unsigned int len = N > 0 ? 1 : 2;
        for (auto n = N; n; len++, n /= 10);
        return len;
    }

    char buf[buflen()] = {};

public:
    constexpr to_string_t() noexcept {
        auto ptr = buf + buflen();
        *--ptr = '\0';

        if (N != 0) {
            for (auto n = N; n; n /= 10)
                *--ptr = "0123456789"[(N < 0 ? -1 : 1) * (n % 10)];
            if (N < 0)
                *--ptr = '-';
        } else {
            buf[0] = '0';
        }
    }

    constexpr operator const char *() const { return buf; }
};

Finally, a variable template (another C++14 addition) simplifies the syntax:

template<std::intmax_t N>
constexpr to_string_t<N> to_string;

puts(to_string<62017>); // prints "62017"

The functionality can be extended to support other bases (e.g. hexadecimal), wide character types, and the common container interface; I've packed this all into a single header and put it on GitHub at tcsullivan/constexpr-to-string.

With C++20, this can also be extended to support floating-point numbers. A container type is needed for the floating-point literal, which previously could not be a template parameter. See the f_to_string.hpp header in the GitHub repo for the implementation.

Auston answered 26/6, 2020 at 14:3 Comment(4)
Excellent! That works great. The only small thing is to be able to choose between upper and lower for hexadecimal.Ensile
Switched the accepted answer to this one since it's more useful in modern C++Burtie
This is effectively a link-only answer to your code on Github. Nothing in this answer shows how the conversion code actually works, or what C++17 features it uses. (Presumably extensions to what's allowed with constexpr.) It would be nice to at least show a minimal version in the answer itself.Tantalite
@PeterCordes You're right, this needed a real explanation. I've updated my answer to be more thorough, and discovered that a C++14 implementation (now shown) is possible.Auston
D
7

Maybe i missed something, but this should be as simple as:

 #define NUM(x) #x

Unfortunately this won't work with non-type template parameters.

Defamation answered 15/7, 2011 at 22:3 Comment(3)
This doesn't work for arbitrary compile time integers, though. NUM(1+1) gives "1+1".Afrit
To allow other macros to be used as parameters, I recommend extra indirection: #define _NUM(x) #x followed by #define NUM(x) _NUM(x).Speculate
@Afrit enums and known constant compile-time values don't work either. eg NUM(foo); where enum { foo = 42 };. The macro will produce "foo" instead of "42".Existential
C
4

One trick I've seen done in situations where you know for a fact you will never have a number outside the range 0..9 is the following:

return '0' + N;

At first blush this is annoyingly limited. However, I'm surprised how many times this condition holds.

Oh, and I'm aware this returns a char rather than a std::string. This is a feature. string isn't a built in language type, so there's no way to create one at compile time.

Count answered 15/7, 2011 at 21:47 Comment(3)
Yeah in this case the integer could be anything.Burtie
This fails the requirement of returning a char *Clynes
@Clynes - A woefully underappreciated part of requirement analysis is to recognize what's really a requirement, vs. what's just an assumption based on implementation expectations. In this case the question literally says "do something like this". The above does in fact do something like that.Count
F
3

Another useful option:

template <int i, bool gTen>
struct UintToStrImpl
{
   UintToStrImpl<i / 10, (i > 99)> c;
   const char c0 = '0' + i % 10;
};

template <int i>
struct UintToStrImpl <i, false> 
{ 
   const char c0 = '0' + i; 
};

template <int i, bool sign>
struct IntToStrImpl
{
   UintToStrImpl<i, (i > 9)> num_;
};

template <int i>
struct IntToStrImpl <i, false>
{
   const char sign = '-';
   UintToStrImpl<-i, (-i > 9)> num_;
};

template <int i>
struct IntToStr
{
   IntToStrImpl<i, (i >= 0)> num_;
   const char end = '\0';
   const char* str = (char*)this;
};

std::cout << IntToStr<-15450>().str;
Fluker answered 2/12, 2018 at 3:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.