c++ string templates boost boost-mpl

C++ convierte un entero en una cadena en tiempo de compilación



string templates (5)

Quiero hacer algo como esto:

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

Parece que la biblioteca boost MPL podría permitir esto, pero realmente no pude averiguar cómo usarlo para lograrlo. es posible?


En primer lugar, si normalmente conoce el número en tiempo de ejecución, puede construir fácilmente la misma cadena. Es decir, si tiene 12 en su programa, también puede tener "12" .

Las macros del preprocesador también pueden agregar comillas a los argumentos, por lo que puede escribir:

#define STRINGIFICATOR(X) #X

Esto, cada vez que escriba STRINGIFICATOR(2) , producirá "2".

Sin embargo, en realidad se puede hacer sin macros (usando metaprogramación en tiempo de compilación). No es sencillo, por lo que no puedo dar el código exacto, pero puedo darle ideas sobre cómo hacerlo:

  1. Escribe una plantilla recursiva usando el número a convertir. La plantilla se repetirá hasta el caso base, es decir, el número es menor que 10.
  2. En cada iteración, puede hacer que el N% 10 dígitos se convierta en un carácter como sugiere TED, y usar mpl::string para construir la cadena de tiempo de compilación que agrega ese carácter.
  3. Terminarás construyendo una mpl::string , que tiene una cadena estática value() .

Me tomé el tiempo para implementarlo como un ejercicio personal. No está mal al final:

#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; }


Otra opción útil:

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;


Sé que esta pregunta tiene ya algunos años, pero quería una solución que utilizara C ++ 11 puro, sin dependencia de impulso. Así que aquí hay un código (con ideas tomadas de esta respuesta a una pregunta diferente ):

/* 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; }

Salida:

five = 5 one ten = 110 minus thirty = -30 eight cubed = 512


Tal vez me perdí algo, pero esto debería ser tan simple como:

#define NUM(x) #x

Lamentablemente, esto no funcionará con parámetros de plantilla que no sean de tipo.


Un truco que he visto en situaciones en las que sabe a ciencia cierta que nunca tendrá un número fuera del rango 0..9 es el siguiente:

return ''0'' + N;

A primera vista esto es muy limitado. Sin embargo, estoy sorprendido de cuántas veces se cumple esta condición.

Oh, y soy consciente de que esto devuelve un char lugar de std::string . Esta es una característica string no es un tipo de lenguaje incorporado, por lo que no hay forma de crear uno en tiempo de compilación.