问题
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?
回答1:
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:
- 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.
- 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. - You'll end up building a
mpl::string
, that has a staticvalue()
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;
}
回答2:
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
回答3:
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.
回答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.
回答5:
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;
来源:https://stackoverflow.com/questions/6713420/c-convert-integer-to-string-at-compile-time