20

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?

Andrey
  • 648
  • 2
  • 5
  • 12

6 Answers6

28

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;
}
Diego Sevilla
  • 27,697
  • 3
  • 55
  • 86
  • 1
    cool, I was just working on this but didn't know how to concatenate the chars :) – Karoly Horvath Jul 15 '11 at 22:22
  • @yi_H: `mpl` provides the type `push_front::type` to define a compile-time string that has char prepended to an existing string. – Diego Sevilla Jul 15 '11 at 22:32
  • @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](http://stackoverflow.com/questions/5869442/c-compile-time-function-execution/5869507#5869507). – ildjarn Jul 18 '11 at 22:03
  • @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. – Diego Sevilla Jul 18 '11 at 23:42
  • This is pretty awesome. I shudder to think how much large numbers would inflate compile time... unless I'm missing something. – Mark McKenna May 31 '12 at 14:01
  • 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. – Diego Sevilla May 31 '12 at 19:25
  • I appreciate the solution but gee, so much code to do so little. – Apprentice Queue Jun 17 '13 at 03:28
21

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
Community
  • 1
  • 1
John Lindgren
  • 745
  • 5
  • 12
  • How to call this from loop in constexpr function? numeric_builder and my compiler says argument is not constant expression? – pixelblender Feb 04 '19 at 05:42
6

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.

Karoly Horvath
  • 91,854
  • 11
  • 113
  • 173
  • 5
    This doesn't work for arbitrary compile time integers, though. `NUM(1+1)` gives `"1+1"`. – hammar Jul 15 '11 at 22:08
  • 2
    To allow other macros to be used as parameters, I recommend extra indirection: `#define _NUM(x) #x` followed by `#define NUM(x) _NUM(x)`. – vanza Jul 15 '11 at 22:09
  • 4
    @hammar 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"`. – greatwolf Jun 26 '13 at 02:45
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;
ifconfig
  • 5,502
  • 7
  • 38
  • 63
Dmytro Dadyka
  • 2,115
  • 5
  • 16
  • 30
3

This is now possible in C++17 and beyond without any external dependencies, with the conversion done entirely at compile-time. I've packaged the code in a short header file, check out constexpr-to-string on GitHub. The code supports different bases and character widths as well.

Examples of use:

const char *number = to_string<2147483648999954564, 16>; // produces "1DCD65003B9A1884"
puts(number);
puts(to_string<-42>); // produces "-42"
puts(to_string<30, 2>); // produces "11110"

// Below requires C++20
puts(f_to_string<3.1415926>); // Defaults to 5-point precision: "3.14159"
puts(f_to_string<{3.1415926, 7}>); // Specify precision: "3.1415926"
clyne
  • 567
  • 2
  • 9
3

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.

T.E.D.
  • 42,800
  • 9
  • 66
  • 134