C ++ moderno nos ha traído un montón de características que anteriormente carecían de lenguaje. Con el fin de obtener un efecto similar, se inventaron muletas impresionantes durante mucho tiempo, que consisten principalmente en paños muy grandes de patrones y macros (a menudo también autógenos). Pero ahora, de vez en cuando, surge una necesidad de oportunidades que aún no están en el idioma. Y comenzamos a reinventar diseños complejos a partir de plantillas y macros nuevamente, generarlos y lograr el comportamiento que necesitamos. Esta es solo una historia así.
Durante el último medio año, necesité dos veces valores que pudieran usarse en los parámetros de la plantilla. Al mismo tiempo, quería tener nombres legibles por humanos para estos valores y excluir la necesidad de declarar estos nombres por adelantado. Las tareas específicas que resolví fueron un tema separado, tal vez más tarde escribiré publicaciones separadas sobre ellas, en algún lugar del centro de "programación anormal". Ahora hablaré sobre el enfoque mediante el cual resolví este problema.
Entonces, cuando se trata de parámetros de plantilla, podemos usar el tipo o el valor de const estático. Para la mayoría de las tareas esto es más que suficiente. Queremos usar identificadores legibles por humanos en los parámetros: declaramos la estructura, enumeración o constante y los usamos. Los problemas comienzan cuando no podemos determinar este identificador por adelantado y queremos hacerlo en su lugar.
Sería posible declarar una estructura o clase directamente en el parámetro de la plantilla. Esto incluso funcionará si la plantilla no hace nada con este parámetro que requiere una descripción completa de la estructura. Además, no podemos controlar el espacio de nombres en el que se declara dicha estructura. Y las sustituciones de plantillas de aspecto completamente idéntico se convertirán en un código completamente diferente si estas líneas están en clases o espacios de nombres vecinos.
Debe usar literales, y de todos los literales en C ++, solo un literal de caracteres y un literal de cadena se pueden llamar legibles. Pero un literal de caracteres está limitado a cuatro caracteres (cuando se usa char32_t), y un literal de cadena es una matriz de caracteres y su valor no se puede pasar a los parámetros de la plantilla.
Resulta una especie de círculo vicioso. Debe declarar algo por adelantado o usar identificadores inconvenientes. Intentemos obtener el idioma al que no está adaptado. ¿Qué pasaría si implementara una macro que haga que algo sea adecuado para usar en argumentos de plantilla a partir de un literal de cadena?
Hagamos la estructura de la cadena.
Primero, hagamos la base de la cadena. En C ++ 11, aparecieron argumentos de plantilla variadic.
Declaramos una estructura que contiene caracteres de cadena en los argumentos:
template <char... Chars> struct String{};
githubFunciona Incluso podemos usar inmediatamente estas líneas como esta:
template <class T> struct Foo {}; Foo<String<'B', 'a', 'r'>> foo;
Ahora arrastre esta línea al tiempo de ejecución
Genial No sería malo poder obtener el valor de esta cadena en tiempo de ejecución. Deje que haya una estructura de plantilla adicional que extraiga argumentos de dicha cadena y haga una constante a partir de ellos:
template <class T> struct Get; template <char... Chars> struct Get<String<Chars...>> { static constexpr char value[] = { Chars... }; };
Esto tambien funciona. Dado que nuestras líneas no contienen '\ 0' al final, debe manejar cuidadosamente esta constante (en mi opinión, es mejor crear inmediatamente una string_view usando la constante y el tamaño de ella en los argumentos del constructor). Uno podría simplemente agregar '\ 0' al final de la matriz, pero esto no es necesario para mis tareas.
Comprueba que podemos manipular tales cadenas
Bien, ¿qué más puedes hacer con esas cuerdas? Por ejemplo concatenar:
template <class A, class B> struct Concatenate; template <char... Chars, char... ExtraChars...> struct Concatenate<String<Chars...>, String<ExtraChars...>> { using type = String<Chars..., ExtraChars...>; };
githubEn principio, puede hacer más o menos cualquier operación (no lo he probado, porque no lo necesito, pero solo puedo imaginar cómo puede buscar una subcadena o incluso reemplazar una subcadena).
Ahora tenemos la pregunta principal, cómo extraer caracteres de un literal de cadena en tiempo de compilación y colocarlos en los argumentos de la plantilla.
Dibuja el búho Escribe una macro.
Comencemos con una forma de poner los caracteres en los argumentos de la plantilla de uno en uno:
template <class T, char c> struct PushBackCharacter; template <char... Chars, char c> struct PushBackCharacter<String<Chars...>, c> { using type = String<Chars..., c>; }; template <char... Chars> struct PushBackCharacter<String<Chars...>, '\0'> { using type = String<Chars...>; };
githubUtilizo una especialización separada para el carácter '\ 0', para no agregarlo a la cadena utilizada. Además, esto simplifica un poco las otras partes de la macro.
La buena noticia es que un literal de cadena puede ser un parámetro para la función constexpr. Escribimos una función que devuelve un carácter por índice en una cadena o '\ 0' si la cadena es más corta que el índice (aquí es útil la especialización PushBackCharacter para el carácter '\ 0').
template <size_t N> constexpr char CharAt(const char (&s)[N], size_t i) { return i < N ? s[i] : '\0'; }
Básicamente, ya podemos escribir algo como esto:
PushBackCharacter< PushBackCharacter< PushBackCharacter< PushBackCharacter< String<>, CharAt("foo", 0) >::type, CharAt("foo", 1) >::type, CharAt("foo", 2) >::type, CharAt("foo", 3) >::type
Ponemos tal paño, pero más genuino (podemos escribir scripts para generar código) dentro de nuestra macro, ¡y eso es todo!
Hay un matiz. Si el número de caracteres en la línea es mayor que los niveles de anidación en la macro, la línea simplemente se cortará y ni siquiera lo notaremos. El desastre
Hagamos una estructura más, que no convierta la cadena que llega de ninguna manera, pero sí static_assert para que su longitud no exceda una constante:
#define _NUMBER_TO_STR(n) #n #define NUMBER_TO_STR(n) _NUMBER_TO_STR(n) template <class String, size_t size> struct LiteralSizeLimiter { using type = String; static_assert(size <= MAX_META_STRING_LITERAL_SIZE, "at most " NUMBER_TO_STR(MAX_META_STRING_LITERAL_SIZE) " characters allowed for constexpr string literal"); }; #undef NUMBER_TO_STR #undef _NUMBER_TO_STR
Bueno, la macro se verá así:
#define MAX_META_STRING_LITERAL_SIZE 256 #define STR(literal) \ ::LiteralSizeLimiter< \ ::PushBackCharacter< \ ... \ ::PushBackCharacter< \ ::String<> \ , ::CharAt(literal, 0)>::type \ ... \ , ::CharAt(literal, 255)>::type \ , sizeof(literal) - 1>::type
githubResultó
template <class S> std::string_view GetContent() { return std::string_view(Get<S>::value, sizeof(Get<S>::value)); } std::cout << GetContent<STR("Hello Habr!")>() << std::endl;
La implementación que obtuve se puede
encontrar en el github .
Sería muy interesante para mí escuchar sobre las posibles aplicaciones de este mecanismo, diferentes de las que se me ocurrieron.