С++ конвертировать целое число в строку во время компиляции
Я хочу сделать что-то вроде этого:
template<int N>
char* foo() {
// return a compile-time string containing N, equivalent to doing
// ostringstream ostr;
// ostr << N;
// return ostr.str().c_str();
}
Похоже, что библиотека расширения MPL может это позволить, но я не мог понять, как ее использовать для достижения этой цели. Это возможно?
Ответы
Ответ 1
Прежде всего, если вы обычно знаете номер во время выполнения, вы можете легко построить одну и ту же строку. То есть, если у вас есть 12
в вашей программе, вы можете также "12"
.
Макросы препроцессора могут также добавлять кавычки к аргументам, поэтому вы можете написать:
#define STRINGIFICATOR(X) #X
Это, всякий раз, когда вы пишете STRINGIFICATOR(2)
, он произведет "2".
Однако это можно сделать без макросов (используя метапрограммирование времени компиляции). Это не просто, поэтому я не могу дать точный код, но я могу дать вам идеи о том, как это сделать:
- Напишите рекурсивный шаблон с использованием числа, которое нужно преобразовать. Шаблон будет возвращаться до базового случая, то есть число меньше 10.
- На каждой итерации вы можете преобразовать символ N% 10 в символ как T.E.D. предлагает и использует
mpl::string
для построения строки времени компиляции, которая добавляет этот символ.
- В итоге вы создадите
mpl::string
со статической строкой value()
.
Я потратил время, чтобы реализовать его как личное упражнение. Неплохо в конце:
#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
Я знаю, что этот вопрос уже несколько лет, но мне нужно решение, использующее чистый С++ 11, без зависимости от boost. Итак, вот какой код (с идеями, заимствованными из этого ответа на другой вопрос):
/* 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;
}
Вывод:
five = 5
one ten = 110
minus thirty = -30
eight cubed = 512
Ответ 3
Возможно, я что-то пропустил, но это должно быть так же просто, как:
#define NUM(x) #x
К сожалению, это не будет работать с параметрами шаблона не-типа.
Ответ 4
Один трюк, который я видел в ситуациях, когда вы знаете, что у вас никогда не будет номера за пределами диапазона 0..9
, будет следующее:
return '0' + N;
Сначала краснеть раздражает. Однако я удивлен, сколько раз это условие выполняется.
О, и я знаю, что это возвращает char
, а не std::string
. Это особенность. string
не является встроенным языком, поэтому нет способа создать его во время компиляции.
Ответ 5
Еще одна полезная опция:
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;