C ++ преобразовать целое число в строку во время компиляции - PullRequest
16 голосов
/ 16 июля 2011

Я хочу сделать что-то вроде этого:

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 boost может позволить это, но я не могу понять, как это использовать для достижения этой цели.Это возможно?

Ответы [ 5 ]

22 голосов
/ 16 июля 2011

Прежде всего, если обычно вы знаете число во время выполнения, вы можете легко построить ту же строку.То есть, если в вашей программе 12, вы также можете иметь "12".

Макросы препроцессора могут также добавлять к аргументам кавычки, поэтому вы можете написать:

#define STRINGIFICATOR(X) #X

Это всякий раз, когда вы пишете STRINGIFICATOR(2), оно будет выдавать «2».

Однако на самом деле можно сделать без макросов (используя метапрограммирование во время компиляции).Это не просто, поэтому я не могу дать точный код, но я могу дать вам идеи, как это сделать:

  1. Напишите рекурсивный шаблон, используя число для преобразования.Шаблон будет повторяться до базового случая, то есть число будет меньше 10.
  2. На каждой итерации вы можете преобразовать цифру N% 10 в символ, как рекомендует TED, и с использованием mpl::string для построения строки времени компиляции, в которую добавляется этот символ.
  3. В итоге вы создадите 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;
}
18 голосов
/ 09 ноября 2014

Я знаю, что этому вопросу уже несколько лет, но я хотел найти решение, использующее чистый C ++ 11, без зависимости от наддува.Итак, вот некоторый код (с идеями, заимствованными из этого ответа на другой вопрос ):

/* 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
4 голосов
/ 16 июля 2011

Может быть, я что-то пропустил, но это должно быть так просто:

 #define NUM(x) #x

К сожалению, это не будет работать с нетиповыми параметрами шаблона.

3 голосов
/ 16 июля 2011

Один трюк, который я видел, был сделан в ситуациях, когда вы точно знаете, что у вас никогда не будет числа вне диапазона 0..9, это следующее:

return '0' + N;

На первый взгляд это раздражающе ограничено. Однако я удивлен, сколько раз это условие выполняется.

О, и я знаю, что это возвращает char, а не std::string. Это особенность. string не является встроенным языковым типом, поэтому его невозможно создать во время компиляции.

2 голосов
/ 02 декабря 2018

Еще одна полезная опция:

template <int i, bool gTen>
struct UintToStrImpl
{
   UintToStrImpl<i / 10, (i > 90)> 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;
...