C ++: ¿Puede una macro expandir “abc” en ‘a’, ‘b’, ‘c’?

Escribí una plantilla variadica que acepta una cantidad variable de parámetros de caracteres, es decir,

 template  struct Foo; 

Me preguntaba si había algún truco macro que me permitiera crear una instancia con syntax similar a la siguiente:

 Foo 

o

 Foo 

o

 Foo 

etc.

Básicamente, cualquier cosa que te impida escribir los caracteres individualmente, como

 Foo 

Este no es un gran problema para mí, ya que es solo para un progtwig de juguetes, pero pensé que podría preguntar de todos modos.

Creé uno hoy y lo probé en GCC4.6.0.

 #include  #define E(L,I) \ (I < sizeof(L)) ? L[I] : 0 #define STR(X, L) \ typename Expand \ cstring<>, sizeof L-1>::type #define CSTR(L) STR(cstring, L) template struct cstring { }; template class P, typename S, typename R, int N> struct Expand; template class P, char S1, char ...S, char ...R, int N> struct Expand, cstring, N> : Expand, cstring, N-1>{ }; template class P, char S1, char ...S, char ...R> struct Expand, cstring, 0> { typedef P type; }; 

Alguna prueba

 template struct Test { static void print() { char x[] = { S... }; std::cout << sizeof...(S) << std::endl; std::cout << x << std::endl; } }; template void process(cstring) { /* process C, possibly at compile time */ } int main() { typedef STR(Test, "Hello folks") type; type::print(); process(CSTR("Hi guys")()); } 

Entonces, si bien no obtienes 'a', 'b', 'c' , todavía obtienes cadenas de tiempo de comstackción.

Ha habido muchas pruebas, pero creo que está condenado al fracaso.

Para entender por qué, uno necesita entender cómo funciona el preprocesador. La entrada del preprocesador se puede considerar como una secuencia. Esta secuencia se transforma primero en tokens de preprocesamiento (lista disponible en The C ++ Programming Language, 3rd Edition, Annexe A Grammar, página 795)

En estos tokens, el preprocesador solo puede aplicar un número muy restringido de operaciones, aparte de las cosas de digtwigs / trigtwigs, esta cantidad a:

  • inclusión de archivos (para directivas de encabezado), esto puede no aparecer en una macro, por lo que sé
  • sustitución de macro (que es extremadamente complicado: p)
  • # : transforma un token en un token de cadena literal (rodeándolo con comillas)
  • ## : concatena dos tokens

Y eso es.

  • No hay instrucciones de preprocesador que puedan dividir un token en varios tokens: esto es una sustitución de macro, lo que significa que realmente tiene una macro definida en primer lugar
  • No hay instrucción de preprocesador para transformar un literal de cadena en un token regular (eliminando las comillas) que luego podría estar sujeto a la sustitución de macro.

Por lo tanto, mantengo la afirmación de que es imposible (ya sea en C ++ 03 o C ++ 0x), aunque es posible que (posiblemente) haya extensiones específicas del comstackdor para esto.

Una solución basada en la respuesta anterior de Sylvain Defresne es posible en C ++ 11:

 #include  #include  template  constexpr char get_ch (char const (&s) [N], unsigned int i) { return i >= N ? '\0' : s[i]; } #define STRING_TO_CHARS_EXTRACT(z, n, data) \ BOOST_PP_COMMA_IF(n) get_ch(data, n) #define STRING_TO_CHARS(STRLEN, STR) \ BOOST_PP_REPEAT(STRLEN, STRING_TO_CHARS_EXTRACT, STR) // Foo  // expands to // Foo <'a', 'b', 'c'> 

Además, siempre que la plantilla en cuestión pueda manejar múltiples caracteres ‘\ 0’ de terminación, podemos facilitar el requisito de longitud a favor de una longitud máxima:

 #define STRING_TO_CHARS_ANY(STR) \ STRING_TO_CHARS(100, STR) // Foo  // expands to // Foo <'a', 'b', 'c', '\0', '\0', ...> 

Los ejemplos anteriores se comstackn correctamente en clang ++ (3.2) y g ++ (4.8.0).

esto solía funcionar en una versión anterior de msvc, no sé si todavía lo hace:

 #define CHAR_SPLIT(...) #@__VA_ARGS__ 

Lamentablemente, creo que esto no se puede hacer. Lo mejor que puede obtener del preprocesador lo proporciona Boost.Preprocessor , principalmente a través de sus tipos de datos:

  • array : la syntax sería (3, (a, b, c))
  • list : la syntax sería (a, (b, (c, BOOST_PP_NIL)))
  • sequence : la syntax sería (a)(b)(c)
  • tuple : la syntax sería (a, b, c)

Desde cualquiera de estos tipos, puede crear fácilmente una macro que construya una lista separada por comas de elementos adjuntos de comillas simples (consulte, por ejemplo, BOOST_PP_SEQ_ENUM ), pero creo que la entrada de esta macro tendrá que ser de uno de estos tipos, y todos requieren que los caracteres se tipeen individualmente.

En base a lo que estaba discutiendo arriba, el siguiente hacker de plantillas horrible puede ser suficiente para lograr esto. No he probado esto (¡lo siento!), Pero estoy bastante seguro de que podría funcionar o algo así.

El primer paso es crear una clase de plantilla que solo contenga una tupla de caracteres:

 template  class CharTuple {}; 

Ahora, construyamos un adaptador que pueda transformar una cadena de estilo C en CharTuple. Para hacer esto, necesitaremos la siguiente clase de ayuda, que es esencialmente una desventaja de estilo LISP para tuplas:

 template  class Cons; template  class Cons> { typedef CharTuple type; } 

Supongamos también que tenemos una statement meta-if:

 template  class If { typedef typename TrueType::type type; }; template  class If { typedef typename FalseType::type type; }; 

Luego, lo siguiente debería permitirle convertir una cadena estilo C en una tupla:

 template  class Identity { typedef T type; }; template  class StringToChars { typedef typename If<*str == '\0', Identity>, Cons<*str, typename StringToChars::type>>::type type; }; 

Ahora que puede convertir una cadena de estilo C en una tupla de caracteres, puede canalizar su cadena de entrada a través de este tipo para recuperar la tupla. Sin embargo, tendremos que hacer un poco más de maquinaria para que funcione. ¿No es TMP divertido? 🙂

El primer paso es tomar tu código original:

 template  class Foo { /* ... */ }; 

y use alguna especialización de plantilla para convertirlo a

 template  class FooImpl; tempalte  class FooImpl> { /* ... */ }; 

Es solo otra capa de indirección; nada mas.

Finalmente, deberías poder hacer esto:

 template  class Foo { typedef typename FooImpl::type>::type type; }; 

Realmente espero que funcione. Si no lo hace, sigo pensando que vale la pena publicarlo porque probablemente sea ε-cerca de una respuesta válida. 🙂

Basado en la solución de user1653543 anterior.

Alguna magia de plantilla:

 template  constexpr char getch (char const (&s) [N], unsigned int i) { return i >= N ? '\0' : s[i]; } template struct split_helper; template struct split_helper { typedef push_front_t::type, char_> type; }; template struct split_helper<'\0', Cs...> { typedef std::integer_sequence type; }; template using split_helper_t = typename split_helper::type; 

Algo de magia de PP:

 #define SPLIT_CHARS_EXTRACT(z, n, data) \ BOOST_PP_COMMA_IF(n) getch(data, n) #define STRING_N(n, str) \ split_helper_t #define STRING(str) STRING_N(BOOST_PP_LIMIT_REPEAT, str) 

split_helper just helper para cortar ceros finales. Ahora STRING("Hello") es una secuencia de caracteres de comstackción en tiempo de escritura ( std::integer_sequence ). La longitud de las constantes de cadena es hasta BOOST_PP_LIMIT_REPEAT caracteres.

Tarea : implementar push_front_t y c_str para obtener una cadena terminada en nulo de std::integer_sequence . (Aunque, puedes intentar usar Boost.MPL)