
Si programa en C ++, probablemente se preguntó por qué no puede comparar dos literales de cadena o realizar su concatenación:
auto str = "hello" + "world";
Sin embargo, como dicen, "es imposible, pero si realmente quieres, entonces puedes". Romperemos los estereotipos debajo del corte, y justo en la etapa de compilación.
¿Por qué se necesita todo esto?
En uno de los proyectos en los que estaba trabajando, era costumbre usar std :: string como constantes de cadena. El proyecto tenía varios módulos en los que se definían constantes de cadena globales:
Creo que ya adivinaste lo que pasó un día. SAMPLE_PLUGIN_PATH
tomó el valor "sample.so"
, a pesar de que PLUGIN_PATH
tenía el valor "/usr/local/lib/project/plugins/"
, como se esperaba. ¿Cómo pudo pasar esto? Todo es muy simple, el orden de inicialización de los objetos globales no está definido, en el momento de la inicialización SAMPLE_PLUGIN_PATH
variable PLUGIN_PATH
estaba vacía.
Además, este enfoque tiene una serie de desventajas. Primero, la excepción lanzada al crear un objeto global no se detecta. En segundo lugar, la inicialización ocurre durante la ejecución del programa, lo que gasta un valioso tiempo de procesador.
Fue entonces cuando tuve la idea de trabajar con cadenas en la etapa de compilación, lo que finalmente llevó a la redacción de este artículo.
En este artículo, consideraremos las líneas que se pueden operar en la etapa de compilación. Llamaremos a estas líneas estáticas.
Todas las operaciones implementadas se incluyeron en la biblioteca para trabajar con cadenas estáticas. El código fuente de la biblioteca está disponible en github, el enlace se encuentra al final del artículo.
Se requiere al menos C ++ 14 para usar la biblioteca.
Definición de cadena estática
Definimos una cadena estática como una matriz de caracteres, por conveniencia asumimos que la cadena siempre termina con un carácter nulo:
template<size_t Size> using static_string = std::array<const char, Size>; constexpr static_string<6> hello = {'H', 'e', 'l', 'l', 'o', '\0'};
Aquí puede ir por un camino diferente y definir la cadena como una tupla de caracteres. Esta opción me pareció más lenta y menos conveniente. Por lo tanto, no se considerará aquí.
Crear una cadena estática
Mira la definición de la línea de saludo anterior, es simplemente horrible. Primero, necesitamos calcular previamente la longitud de la matriz. En segundo lugar, debe recordar escribir el carácter nulo al final. En tercer lugar, todas estas comas, corchetes y comillas. Definitivamente hay algo que hacer al respecto. Me gustaría escribir algo como esto:
constexpr auto hello = make_static_string("hello");
Aquí una de las formas de la plantilla variable nos ayudará, lo que nos permite expandir los argumentos de la plantilla como índices para la inicialización agregada de nuestra cadena estática desde un literal de cadena:
template<size_t Size, size_t ... Indexes> constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const char (& str)[Size]) { return {str[Indexes] ..., '\0'}; } constexpr auto hello = make_static_string<0, 1, 2, 3, 4>("hello");
Ya es mejor, pero los índices aún deben escribirse a mano. Aquí también notamos que si no especifica todos los índices, puede obtener la subcadena de la cadena literal, y si los escribe en el orden inverso, entonces es inverso:
constexpr hello1 = make_static_string<1, 2, 3>("hello");
Esta consideración nos será muy útil en el futuro.
Ahora necesitamos generar de alguna manera una secuencia de índices de fila. Para hacer esto, aplique el truco de herencia. Defina una estructura vacía (necesita heredar algo) con un conjunto de índices requeridos como parámetros de plantilla:
template<size_t ... Indexes> struct index_sequence {};
Definimos una estructura generadora que generará índices uno a la vez, almacenando el contador en el primer parámetro:
template<size_t Size, size_t ... Indexes> struct make_index_sequence : make_index_sequence<Size - 1, Size - 1, Indexes ...> {};
También nos ocuparemos del punto final de recursión, cuando se generan todos los índices (el contador es cero), descartamos el contador y el generador se convierte en la secuencia que necesitamos:
template<size_t ... Indexes> struct make_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {};
Como resultado, la función de crear una cadena estática se verá así:
template<size_t Size, size_t ... Indexes> constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const char (& str)[Size], index_sequence<Indexes ...>) { return {str[Indexes] ..., '\0'}; }
Escribiremos una función similar para una cadena estática, nos será más útil:
template<size_t Size, size_t ... Indexes> constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const static_string<Size>& str, index_sequence<Indexes ...>) { return {str[Indexes] ..., '\0'}; }
Además, para cada función que toma el string literal foo(const char (& str)[Size])
escribiremos una función similar que tome el string estático foo(const static_string<Size>& str)
. Pero por brevedad, no mencionaré esto.
Como conocemos la longitud del literal de cadena, podemos generar automáticamente una secuencia de índices, escribiremos un contenedor para la función anterior:
template<size_t Size> constexpr static_string<Size> make_static_string(const char (& str)[Size]) { return make_static_string(str, make_index_sequence<Size - 1>{}); }
Esta función nos permite hacer exactamente lo que queríamos al comienzo del capítulo.
Si no hay argumentos, devolveremos una cadena estática vacía, que consiste solo en un carácter nulo:
constexpr static_string<1> make_static_string() { return {'\0'}; }
También necesitamos crear una cadena a partir de una tupla de caracteres:
template<char ... Chars> constexpr static_string<sizeof ... (Chars) + 1> make_static_string(char_sequence<Chars ...>) { return {Chars ..., '\0'}; }
Por cierto, todo lo que se describirá más adelante en este artículo se basa en las técnicas descritas en este capítulo. Por lo tanto, si algo sigue siendo incomprensible, es mejor volver a leer el capítulo nuevamente.
Salida de una cadena estática a una secuencia
Todo es simple aquí. Como nuestra cadena termina con un carácter nulo, es suficiente para generar los datos de la matriz en la secuencia:
template<size_t Size> std::ostream& operator<<(std::ostream& os, const static_string<Size>& str) { os << str.data(); return os; }
Convertir cadena estática a std :: string
Nada complicado aquí tampoco. Inicializamos la cadena con datos de matriz:
template<size_t Size> std::string to_string(const static_string<Size>& str) { return std::string(str.data()); }
Comparación de cadenas estáticas
Compararemos las líneas carácter por carácter hasta que encontremos las diferencias, o hasta que lleguemos al final de al menos una de las líneas. Como constexpr para aún no se ha inventado, utilizamos la recursividad y el operador ternario:
template<size_t Size1, size_t Size2> constexpr int static_string_compare( const static_string<Size1>& str1, const static_string<Size2>& str2, int index = 0) { return index >= Size1 && index >= Size2 ? 0 : index >= Size1 ? -1 : index >= Size2 ? 1 : str1[index] > str2[index] ? 1 : str1[index] < str2[index] ? -1 : static_string_compare(str1, str2, index + 1); }
En el futuro, necesitaremos una versión extendida del comparador, introduciremos un índice individual para cada una de sus líneas y también limitaremos el número de caracteres comparados:
template<size_t Size1, size_t Size2> constexpr int static_string_compare( const static_string<Size1>& str1, size_t index1, const static_string<Size2>& str2, size_t index2, size_t cur_length, size_t max_length) { return cur_length > max_length || (index1 >= Size1 && index2 >= Size2) ? 0 : index1 >= Size1 ? -1 : index2 >= Size2 ? 1 : str1[index1] > str2[index2] ? 1 : str1[index1] < str2[index2] ? -1 : static_string_compare(str1, index1 + 1, str2, index2 + 1, cur_length + 1, max_length); }
Esta versión del comparador nos permitirá comparar no solo la cadena completa, sino también las subcadenas individuales.
Concatenación de cadenas estáticas
Para la concatenación, utilizamos la misma plantilla variable que en el capítulo sobre la creación de una cadena estática. Inicialice la matriz primero con los caracteres de la primera línea (excluyendo el carácter nulo), luego la segunda, y finalmente agregue el carácter nulo al final:
template<size_t Size1, size_t ... Indexes1, size_t Size2, size_t ... Indexes2> constexpr static_string<Size1 + Size2 - 1> static_string_concat_2( const static_string<Size1>& str1, index_sequence<Indexes1 ...>, const static_string<Size2>& str2, index_sequence<Indexes2 ...>) { return {str1[Indexes1] ..., str2[Indexes2] ..., '\0'}; } template<size_t Size1, size_t Size2> constexpr static_string<Size1 + Size2 - 1> static_string_concat_2( const static_string<Size1>& str1, const static_string<Size2>& str2) { return static_string_concat_2(str1, make_index_sequence<Size1 - 1>{}, str2, make_index_sequence<Size2 - 1>{}); }
También implementamos una plantilla variable para concatenar un número arbitrario de cadenas o literales de cadena:
constexpr auto static_string_concat() { return make_static_string(); } template<typename Arg, typename ... Args> constexpr auto static_string_concat(Arg&& arg, Args&& ... args) { return static_string_concat_2(make_static_string(std::forward<Arg>(arg)), static_string_concat(std::forward<Args>(args) ...)); }
Operaciones de búsqueda de cadenas estáticas
Considere la operación de encontrar un carácter y una subcadena en una cadena estática.
Busca un personaje en una cadena estática
La búsqueda de caracteres no es particularmente difícil, verifique recursivamente los caracteres para todos los índices y devuelva el primer índice en caso de una coincidencia. También le daremos la oportunidad de establecer la posición inicial de la búsqueda y el número de secuencia de la coincidencia:
template<size_t Size> constexpr size_t static_string_find(const static_string<Size>& str, char ch, size_t from, size_t nth) { return Size < 2 || from >= Size - 1 ? static_string_npos : str[from] != ch ? static_string_find(str, ch, from + 1, nth) : nth > 0 ? static_string_find(str, ch, from + 1, nth - 1) : from; }
La constante static_string_npos
indica que la búsqueda no tuvo éxito. Lo definimos de la siguiente manera:
constexpr size_t static_string_npos = std::numeric_limits<size_t>::max();
Del mismo modo, implementamos una búsqueda en la dirección opuesta:
template<size_t Size> constexpr size_t static_string_rfind(const static_string<Size>& str, char ch, size_t from, size_t nth) { return Size < 2 || from > Size - 2 ? static_string_npos : str[from] != ch ? static_string_rfind(str, ch, from - 1, nth) : nth > 0 ? static_string_rfind(str, ch, from - 1, nth - 1) : from; }
Determinar la aparición de un carácter en una cadena estática
Para determinar la aparición de un personaje, solo intenta buscarlo:
template<size_t Size> constexpr bool static_string_contains(const static_string<Size>& str, char ch) { return static_string_find(str, ch) != static_string_npos; }
Cuenta el número de apariciones de un personaje en una cadena estática
El recuento de la cantidad de ocurrencias se implementa trivialmente:
template<size_t Size> constexpr size_t static_string_count(const static_string<Size>& str, char ch, size_t index) { return index >= Size - 1 ? 0 : (str[index] == ch ? 1 : 0) + static_string_count(str, ch, index + 1); }
Buscar una subcadena en una cadena estática
Como se supone que las filas estáticas serán relativamente pequeñas, no implementaremos el algoritmo Knut-Morris-Pratt aquí, implementaremos el algoritmo cuadrático más simple:
template<size_t Size, size_t SubSize> constexpr size_t static_string_find(const static_string<Size>& str, const static_string<SubSize>& substr, size_t from, size_t nth) { return Size < SubSize || from > Size - SubSize ? static_string_npos : static_string_compare(str, from, substr, 0, 1, SubSize - 1) != 0 ? static_string_find(str, substr, from + 1, nth) : nth > 0 ? static_string_find(str, substr, from + 1, nth - 1) : from; }
Del mismo modo, implementamos una búsqueda en la dirección opuesta:
template<size_t Size, size_t SubSize> constexpr size_t static_string_rfind(const static_string<Size>& str, const static_string<SubSize>& substr, size_t from, size_t nth) { return Size < SubSize || from > Size - SubSize ? static_string_npos : static_string_compare(str, from, substr, 0, 1, SubSize - 1) != 0 ? static_string_rfind(str, substr, from - 1, nth) : nth > 0 ? static_string_rfind(str, substr, from - 1, nth - 1) : from; }
Determinar la aparición de una subcadena en una cadena estática
Para determinar la aparición de una subcadena, intente buscarla:
template<size_t Size, size_t SubSize> constexpr bool static_string_contains(const static_string<Size>& str, const static_string<SubSize>& substr) { return static_string_find(str, substr) != static_string_npos; }
Determinar si una cadena estática comienza / termina con / en una subcadena dada
Aplicando el comparador descrito anteriormente, podemos determinar si la cadena estática comienza con la subcadena dada:
template<size_t SubSize, size_t Size> constexpr bool static_string_starts_with(const static_string<Size>& str, const static_string<SubSize>& prefix) { return SubSize > Size ? false : static_string_compare(str, 0, prefix, 0, 1, SubSize - 1) == 0; }
Del mismo modo para terminar una línea estática:
template<size_t SubSize, size_t Size> constexpr bool static_string_ends_with(const static_string<Size>& str, const static_string<SubSize>& suffix) { return SubSize > Size ? false : static_string_compare(str, Size - SubSize, suffix, 0, 1, SubSize - 1) == 0; }
Trabajar con subcadenas de cadenas estáticas
Aquí observamos las operaciones asociadas con las subcadenas de una cadena estática.
Obtener subcadena, prefijo y sufijo de una cadena estática
Como notamos anteriormente, para obtener una subcadena, debe generar una secuencia de índices, con los índices de inicio y fin dados:
template<size_t Begin, size_t End, size_t ... Indexes> struct make_index_subsequence : make_index_subsequence<Begin, End - 1, End - 1, Indexes ...> {}; template<size_t Pos, size_t ... Indexes> struct make_index_subsequence<Pos, Pos, Indexes ...> : index_sequence<Indexes ...> {};
Implementamos la obtención de una subcadena con la comprobación del comienzo y el final de una subcadena usando static_assert
:
template<size_t Begin, size_t End, size_t Size> constexpr auto static_string_substring(const static_string<Size>& str) { static_assert(Begin <= End, "Begin is greater than End (Begin > End)"); static_assert(End <= Size - 1, "End is greater than string length (End > Size - 1)"); return make_static_string(str, make_index_subsequence<Begin, End>{}); }
El prefijo es una subcadena cuyo comienzo coincide con el comienzo de la línea estática original:
template<size_t End, size_t Size> constexpr auto static_string_prefix(const static_string<Size>& str) { return static_string_substring<0, End>(str); }
De manera similar para el sufijo, solo el final coincide:
template<size_t Begin, size_t Size> constexpr auto static_string_suffix(const static_string<Size>& str) { return static_string_substring<Begin, Size - 1>(str); }
Dividir una cadena estática en dos partes en un índice dado
Para dividir una cadena estática en un índice dado, es suficiente devolver el prefijo y el sufijo:
template<size_t Index, size_t Size> constexpr auto static_string_split(const static_string<Size>& str) { return std::make_pair(static_string_prefix<Index>(str), static_string_suffix<Index + 1>(str)); }
Inversión de cuerda estática
Para invertir una cadena estática, escribimos un generador de índice que genera índices en el orden inverso:
template<size_t Size, size_t ... Indexes> struct make_reverse_index_sequence : make_reverse_index_sequence<Size - 1, Indexes ..., Size - 1> {}; template<size_t ... Indexes> struct make_reverse_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {};
Ahora implementamos una función que invierte la cadena estática:
template<size_t Size> constexpr auto static_string_reverse(const static_string<Size>& str) { return make_static_string(str, make_reverse_index_sequence<Size - 1>{}); }
Cálculo de Hash de Cadena Estática
Calcularemos el hash usando la siguiente fórmula:
H (s) = (s 0 + 1) ⋅ 33 0 + (s 1 + 1) ⋅ 33 1 + ... + (s n - 1 + 1) ⋅ 33 n - 1 + 5381 ⋅ 33 n mod 2 64
template<size_t Size> constexpr unsigned long long static_string_hash(const static_string<Size>& str, size_t index) { return index >= Size - 1 ? 5381ULL : static_string_hash(str, index + 1) * 33ULL + str[index] + 1; }
Convierte un número en una cadena estática y viceversa
En este capítulo, veremos la conversión de una cadena estática a un número entero, así como a la inversa. Para simplificar, suponemos que los números están representados por tipos long long
y unsigned long long
, estos son tipos de gran capacidad, es decir, son adecuados para la mayoría de los casos.
Convierte un número en una cadena estática
Para convertir un número en una cadena estática, necesitamos obtener todos los dígitos del número, convertirlos a los caracteres correspondientes y hacer una cadena de estos caracteres.
Para obtener todos los dígitos de un número, utilizaremos un generador similar al generador de secuencia de índice. Defina una secuencia de caracteres:
template<char ... Chars> struct char_sequence {};
Implementamos un generador de caracteres de dígitos, almacenando el número actual en el primer parámetro, y los números en el siguiente, el siguiente dígito se agrega al comienzo de la secuencia, y el número se divide por diez:
template<unsigned long long Value, char ... Chars> struct make_unsigned_int_char_sequence : make_unsigned_int_char_sequence<Value / 10, '0' + Value % 10, Chars ...> {};
Si el número actual es 0, lo descartamos, devolviendo una secuencia de dígitos, no hay nada más para convertir:
template<char ... Chars> struct make_unsigned_int_char_sequence<0, Chars ...> : char_sequence<Chars ...> {};
También debe tener en cuenta el caso cuando el número inicial es cero, en este caso debe devolver un carácter nulo; de lo contrario, el cero se convertirá en una secuencia vacía de caracteres y luego en una cadena vacía:
template<> struct make_unsigned_int_char_sequence<0> : char_sequence<'0'> {};
El generador implementado funciona muy bien para números positivos, pero no es adecuado para números negativos. Definimos un nuevo generador agregando un parámetro de plantilla más al principio: el signo del número convertido:
template<bool Negative, long long Value, char ... Chars> struct make_signed_int_char_sequence {};
Procesaremos el número como se muestra arriba, pero teniendo en cuenta el signo:
template<long long Value, char ... Chars> struct make_signed_int_char_sequence<true, Value, Chars ...> : make_signed_int_char_sequence<true, Value / 10, '0' + -(Value % 10), Chars ...> {}; template<long long Value, char ... Chars> struct make_signed_int_char_sequence<false, Value, Chars ...> : make_signed_int_char_sequence<false, Value / 10, '0' + Value % 10, Chars ...> {};
Aquí hay un punto sutil, preste atención a -(Value % 10)
. Aquí, no puede -Value % 10
, ya que el rango de números negativos es un número más amplio que el rango de positivos y el módulo de número mínimo cae fuera del conjunto de valores válidos.
Descartamos el número después del procesamiento, si es negativo, agregue el símbolo de signo menos:
template<char ... Chars> struct make_signed_int_char_sequence<true, 0, Chars ...> : char_sequence<'-', Chars ...> {}; template<char ... Chars> struct make_signed_int_char_sequence<false, 0, Chars ...> : char_sequence<Chars ...> {};
Por separado, nos encargamos de convertir cero:
template<> struct make_signed_int_char_sequence<false, 0> : char_sequence<'0'> {};
Finalmente, implementamos las funciones de conversión:
template<unsigned long long Value> constexpr auto uint_to_static_string() { return make_static_string(make_unsigned_int_char_sequence<Value>{}); } template<long long Value> constexpr auto int_to_static_string() { return make_static_string(make_signed_int_char_sequence<(Value < 0), Value>{}); }
Convertir cadena estática a número
Para convertir una cadena estática en un número, debe convertir los caracteres en números y luego sumarlos, multiplicando previamente decenas por la potencia correspondiente. Realizamos todas las acciones de forma recursiva, para una cadena vacía devolvemos cero:
template<size_t Size> constexpr unsigned long long static_string_to_uint(const static_string<Size>& str, size_t index) { return Size < 2 || index >= Size - 1 ? 0 : (str[index] - '0') + 10ULL * static_string_to_uint(str, index - 1); } template<size_t Size> constexpr unsigned long long static_string_to_uint(const static_string<Size>& str) { return static_string_to_uint(str, Size - 2); }
Para convertir números con signo, tenga en cuenta que los números negativos comienzan con un signo menos:
template<size_t Size> constexpr long long static_string_to_int(const static_string<Size>& str, size_t index, size_t first) { return index < first || index >= Size - 1 ? 0 : first == 0 ? (str[index] - '0') + 10LL * static_string_to_int(str, index - 1, first) : -(str[index] - '0') + 10LL * static_string_to_int(str, index - 1, first); } template<size_t Size> constexpr long long static_string_to_int(const static_string<Size>& str) { return Size < 2 ? 0 : str[0] == '-' ? static_string_to_int(str, Size - 2, 1) : static_string_to_int(str, Size - 2, 0); }
Consideraciones de usabilidad de la biblioteca
En este punto, ya es posible utilizar completamente la biblioteca, pero algunos puntos son inconvenientes. En este capítulo, veremos cómo puede hacer que el uso de la biblioteca sea más conveniente.
Objeto de cadena estática
Empaque la cadena y los métodos implementados en un objeto. Esto permitirá el uso de nombres de métodos más cortos, así como también implementará operadores de comparación:
template<size_t Size> struct static_string { constexpr size_t length() const { return Size - 1; } constexpr size_t size() const { return Size; } constexpr size_t begin() const { return 0; } constexpr size_t end() const { return Size - 1; } constexpr size_t rbegin() const { return Size - 2; } constexpr size_t rend() const { return std::numeric_limits<size_t>::max(); } constexpr bool empty() const { return Size < 2; } constexpr auto reverse() const { return static_string_reverse(*this); } template<size_t Begin, size_t End> constexpr auto substring() const { return static_string_substring<Begin, End>(*this); } template<size_t End> constexpr auto prefix() const { return static_string_prefix<End>(*this); } template<size_t Begin> constexpr auto suffix() const { return static_string_suffix<Begin>(*this); } constexpr size_t find(char ch, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t find(const static_string<SubSize>& substr, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t find(const char (& substr)[SubSize], size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } constexpr size_t rfind(char ch, size_t from = Size - 2, size_t nth = 0) const { return static_string_rfind(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t rfind(const static_string<SubSize>& substr, size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t rfind(const char (& substr)[SubSize], size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } constexpr bool contains(char ch) const { return static_string_contains(*this, ch); } template<size_t SubSize> constexpr bool contains(const static_string<SubSize>& substr) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool contains(const char (& substr)[SubSize]) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool starts_with(const static_string<SubSize>& prefix) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool starts_with(const char (& prefix)[SubSize]) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool ends_with(const static_string<SubSize>& suffix) const { return static_string_ends_with(*this, suffix); } template<size_t SubSize> constexpr bool ends_with(const char (& suffix)[SubSize]) const { return static_string_ends_with(*this, suffix); } constexpr size_t count(char ch) const { return static_string_count(*this, ch); } template<size_t Index> constexpr auto split() const { return static_string_split<Index>(*this); } constexpr unsigned long long hash() const { return static_string_hash(*this); } constexpr char operator[](size_t index) const { return data[index]; } std::string str() const { return to_string(*this); } std::array<const char, Size> data; };
. :
template<size_t Size1, size_t Size2> constexpr bool operator<(const static_string<Size1>& str1, const static_string<Size2>& str2) { return static_string_compare(str1, str2) < 0; }
> <= >= == !=, . - .
:
#define ITOSS(x) int_to_static_string<(x)>() #define UTOSS(x) uint_to_static_string<(x)>() #define SSTOI(x) static_string_to_int((x)) #define SSTOU(x) static_string_to_uint((x))
.
:
constexpr auto hello = make_static_string("Hello"); constexpr auto world = make_static_string("World"); constexpr auto greeting = hello + ", " + world + "!";
, :
constexpr int apples = 5; constexpr int oranges = 7; constexpr auto message = static_string_concat("I have ", ITOSS(apples), " apples and ", ITOSS(oranges), ", so I have ", ITOSS(apples + oranges), " fruits");
constexpr unsigned long long width = 123456789ULL; constexpr unsigned long long height = 987654321ULL; constexpr auto message = static_string_concat("A rectangle with width ", UTOSS(width), " and height ", UTOSS(height), " has area ", UTOSS(width * height));
constexpr long long revenue = 1'000'000LL; constexpr long long costs = 1'200'000LL; constexpr long long profit = revenue - costs; constexpr auto message = static_string_concat("The first quarter has ended with net ", (profit >= 0 ? "profit" : "loss "), " of $", ITOSS(profit < 0 ? -profit : profit));
URL:
constexpr auto url = make_static_string("http://www.server.com:8080"); constexpr auto p = url.find("://"); constexpr auto protocol = url.prefix<p>();
:
constexpr auto str = make_static_string("Hello"); for (size_t i = str.begin(); i != str.end(); ++i)
Referencias
, , github
, .
Update
_ss :
template<typename Char, Char ... Chars> constexpr basic_static_string<Char, sizeof ... (Chars) + 1> operator"" _ss() { return {Chars ..., static_cast<Char>('\0')}; };
make_static_string() , :
constexpr auto hello_world = "Hello"_ss + " World"; if ("Hello" < "World"_ss) { ... } constexpr auto hash = "VeryLongString"_ss.hash();
Char char:
template<typename Char, size_t Size> struct basic_static_string {
char whar_t, , concat, :
template<size_t Size> using static_string_t = basic_static_string<char, Size>; template<size_t Size> using static_wstring_t = basic_static_string<wchar_t, Size>; using static_string = basic_static_string<char, 0>; using static_wstring = basic_static_string<wchar_t, 0>;
"" :
constexpr auto wide_string = L"WideString"_ss; constexpr int apples = 5; constexpr int oranges = 7; constexpr int fruits = apples + oranges; constexpr auto str3 = static_wstring::concat(L"I have ", ITOSW(apples), L" apples and ", ITOSW(oranges), L" oranges, so I have ", ITOSW(fruits), L" fruits"); static_assert(str3 == L"I have 5 apples and 7 oranges, so I have 12 fruits", ""); std::wcout << str3 << std::endl;
size(), size() length() , sizeof():
constexpr auto ss1 = "Hello"_ss; static_assert(ss1.length() == 5, ""); static_assert(ss1.size() == 5, ""); static_assert(sizeof(ss1) == 6, "");
github
.
Update 2
AndreySu , :
#include <iostream> using namespace std; template<typename Char, Char ... Chars> struct static_string{}; template<typename Char, Char ... Chars1, Char ... Chars2> constexpr static_string<Char, Chars1 ..., Chars2 ... > operator+( const static_string<Char, Chars1 ... >& str1, const static_string<Char, Chars2 ... >& str2) { return static_string<Char, Chars1 ..., Chars2 ...>{}; } template<typename Char, Char ch, Char ... Chars> std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& bos, const static_string<Char, ch, Chars ...>& str) { bos << ch << static_string<Char, Chars ... >{}; return bos; } template<typename Char> std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& bos, const static_string<Char>& str) { return bos; } template<typename Char, Char ... Chars> constexpr static_string<Char, Chars ... > operator"" _ss() { return static_string<Char, Chars ... >{}; }; int main() { constexpr auto str1 = "abc"_ss; constexpr auto str2 = "def"_ss; constexpr auto str = str1 + str2 + str1; std::cout << str << std::endl; return 0; }
, , - .