Trabalhando com seqüências de caracteres no estágio de compilação no C ++ moderno


Se você programa em C ++, provavelmente se pergunta por que não pode comparar dois literais de seqüência de caracteres ou executar sua concatenação:


auto str = "hello" + "world"; //   if ("hello" < "world") { // ,    ,   // ... } 

No entanto, como se costuma dizer, "é impossível, mas se você realmente quiser, poderá." Iremos quebrar estereótipos sob o corte e logo na fase de compilação.


Por que tudo isso é necessário


Em um dos projetos em que eu estava trabalhando, era costume usar std :: string como constantes de string. O projeto tinha vários módulos nos quais constantes globais de string foram definidas:


 // plugin.h const std::string PLUGIN_PATH = "/usr/local/lib/project/plugins/"; // ... 

 // sample_plugin.h const std::string SAMPLE_PLUGIN_LIB = PLUGIN_PATH + "sample.so"; // ... 

Eu acho que você já adivinhou o que aconteceu um dia. SAMPLE_PLUGIN_PATH assumiu o valor "sample.so" , apesar de PLUGIN_PATH ter o valor "/usr/local/lib/project/plugins/" , conforme o esperado. Como isso pôde acontecer? Tudo é muito simples, a ordem de inicialização dos objetos globais não está definida, no momento da inicialização SAMPLE_PLUGIN_PATH variável PLUGIN_PATH estava vazia.


Além disso, essa abordagem tem várias desvantagens. Primeiro, a exceção lançada ao criar um objeto global não é capturada. Em segundo lugar, a inicialização ocorre durante a execução do programa, o que gasta um tempo valioso do processador.


Foi então que tive a ideia de trabalhar com strings na fase de compilação, o que acabou levando à redação deste artigo.


Neste artigo, consideraremos as linhas que podem ser operadas no estágio de compilação. Vamos chamar essas linhas de estáticas.


Todas as operações implementadas foram incluídas na biblioteca para trabalhar com cadeias estáticas. O código fonte da biblioteca está disponível no github, o link está no final do artigo.


É necessário pelo menos C ++ 14 para usar a biblioteca.


Definição de cadeia estática


Definimos uma string estática como uma matriz de caracteres, por conveniência, assumimos que a string sempre termina com um caractere nulo:


 template<size_t Size> using static_string = std::array<const char, Size>; constexpr static_string<6> hello = {'H', 'e', 'l', 'l', 'o', '\0'}; 

Aqui você pode seguir um caminho diferente e definir a sequência como uma tupla de caracteres. Essa opção me pareceu mais demorada e menos conveniente. Portanto, não será considerado aqui.


Criando uma sequência estática


Olhe para a definição da linha Olá acima, é simplesmente horrível. Primeiro, precisamos pré-calcular o comprimento da matriz. Em segundo lugar, você deve se lembrar de escrever o caractere nulo no final. Em terceiro lugar, todas essas vírgulas, colchetes e aspas. Definitivamente, há algo a ser feito sobre isso. Eu gostaria de escrever algo como isto:


 constexpr auto hello = make_static_string("hello"); 

Aqui, uma das formas do modelo de variável nos ajudará, o que nos permite expandir os argumentos do modelo como índices para a inicialização agregada de nossa string estática a partir de uma literal de string:


 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"); // hello == "hello" 

Já é melhor, mas os índices ainda precisam ser escritos à mão. Aqui também observamos que, se você não especificar todos os índices, poderá obter uma substring de uma string literal e, se você os escrever na ordem inversa, será inverso:


 constexpr hello1 = make_static_string<1, 2, 3>("hello"); // hello1 == "ell" constexpr hello2 = make_static_string<4, 3, 2, 1, 0>("hello"); // hello2 == "olleh" 

Essa consideração será muito útil para nós no futuro.


Agora, precisamos gerar de alguma forma uma sequência de índices de linha. Para fazer isso, aplique o truque de herança. Defina uma estrutura vazia (você precisa herdar algo) com um conjunto de índices necessários como parâmetros do modelo:


 template<size_t ... Indexes> struct index_sequence {}; 

Definimos uma estrutura de gerador que irá gerar índices um de cada vez, armazenando o contador no primeiro parâmetro:


 template<size_t Size, size_t ... Indexes> struct make_index_sequence : make_index_sequence<Size - 1, Size - 1, Indexes ...> {}; 

Também cuidaremos do ponto final da recursão, quando todos os índices forem gerados (o contador é zero), descartamos o contador e o gerador se transforma na sequência que precisamos:


 template<size_t ... Indexes> struct make_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {}; 

Como resultado, a função de criar uma string estática ficará assim:


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

Escreveremos uma função semelhante para uma string estática; será útil para nós ainda mais:


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

Além disso, para cada função que pega a string literal foo(const char (& str)[Size]) , escreveremos uma função semelhante que pega a string estática foo(const static_string<Size>& str) . Mas, por uma questão de brevidade, não vou mencionar isso.


Como o comprimento da literal da string é conhecido por nós, podemos gerar automaticamente uma sequência de índices, escreveremos um wrapper para a função acima:


 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>{}); } 

Essa função nos permite fazer exatamente o que queríamos no início do capítulo.


Se não houver argumentos, retornaremos uma string estática vazia, que consiste apenas em um caractere nulo:


 constexpr static_string<1> make_static_string() { return {'\0'}; } 

Também precisamos criar uma string a partir de uma tupla de caracteres:


 template<char ... Chars> constexpr static_string<sizeof ... (Chars) + 1> make_static_string(char_sequence<Chars ...>) { return {Chars ..., '\0'}; } 

A propósito, tudo o que será descrito mais adiante neste artigo é baseado nas técnicas descritas neste capítulo. Portanto, se algo permanecer incompreensível, é melhor reler o capítulo novamente.


Saída de uma sequência estática para um fluxo


Tudo é simples aqui. Como nossa string termina com um caractere nulo, basta enviar os dados da matriz para o fluxo:


 template<size_t Size> std::ostream& operator<<(std::ostream& os, const static_string<Size>& str) { os << str.data(); return os; } 

Converter string estática em std :: string


Nada complicado aqui também. Inicializamos a string com dados da matriz:


 template<size_t Size> std::string to_string(const static_string<Size>& str) { return std::string(str.data()); } 

Comparação estática de cadeias


Compararemos as linhas caractere por caractere até encontrarmos as diferenças ou até chegarmos ao final de pelo menos uma das linhas. Como o constexpr for ainda não foi inventado, usamos a recursão e o operador ternário:


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

No futuro, precisaremos de uma versão estendida do comparador, apresentaremos um índice individual para cada uma de suas linhas e também limitaremos o 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 versão do comparador nos permitirá comparar não apenas a cadeia inteira, mas também substrings individuais.


Concatenação de cadeias estáticas


Para concatenação, usamos o mesmo modelo de variável que no capítulo sobre criação de uma sequência estática. Inicialize a matriz primeiro com os caracteres da primeira linha (excluindo o caractere nulo), depois a segunda e, finalmente, adicione o caractere nulo ao 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>{}); } 

Também implementamos um modelo de variável para concatenar um número arbitrário de strings ou literais de strings:


 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) ...)); } 

Operações de pesquisa de cadeia estática


Considere a operação de procurar um caractere e uma substring em uma sequência estática.


Procure um caractere em uma sequência estática


A pesquisa de caracteres não é particularmente difícil, verifique recursivamente os caracteres para todos os índices e retorne o primeiro índice em caso de correspondência. Também daremos a oportunidade de definir a posição inicial da pesquisa e o número de sequência da correspondência:


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

A constante static_string_npos indica que a pesquisa não teve êxito. Nós o definimos da seguinte forma:


 constexpr size_t static_string_npos = std::numeric_limits<size_t>::max(); 

Da mesma forma, implementamos uma pesquisa na direção oposta:


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

Determinando a ocorrência de um caractere em uma sequência estática


Para determinar a ocorrência de um personagem, tente procurá-lo:


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

Contar o número de ocorrências de um caractere em uma sequência estática


A contagem do número de ocorrências é implementada 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); } 

Procure uma substring em uma sequência estática


Como se supõe que as linhas estáticas serão relativamente pequenas, não implementaremos o algoritmo Knut-Morris-Pratt aqui, implementaremos o algoritmo quadrático mais simples:


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

Da mesma forma, implementamos uma pesquisa na direção oposta:


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

Determinando a ocorrência de uma substring em uma sequência estática


Para determinar a ocorrência de uma substring, tente procurá-la:


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

Determinando se uma sequência estática inicia / termina com / em uma determinada substring


Aplicando o comparador descrito anteriormente, podemos determinar se a sequência estática começa com a substring fornecida:


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

Da mesma forma para terminar uma linha 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; } 

Trabalhando com substrings de cadeia estática


Aqui, examinamos as operações associadas às substrings de uma string estática.


Obtendo substring, prefixo e sufixo de uma string estática


Como observamos anteriormente, para obter uma substring, você precisa gerar uma sequência de índices, com os índices iniciais e finais fornecidos:


 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 a obtenção de uma substring com a verificação do início e do fim de uma substring 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>{}); } 

O prefixo é uma substring cujo início coincide com o início da linha 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); } 

Da mesma forma para o sufixo, apenas o final corresponde:


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

Dividindo uma sequência estática em duas partes em um determinado índice


Para dividir uma sequência estática em um determinado índice, basta retornar o prefixo e o sufixo:


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

Reversão estática de string


Para reverter uma string estática, escrevemos um gerador de índice que gera índices na ordem inversa:


 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 ...> {}; 

Agora, implementamos uma função que reverte a string 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 cadeia estática


Vamos calcular o hash usando a seguinte 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; } 

Converter um número em uma sequência estática e vice-versa


Neste capítulo, examinamos a conversão de uma sequência estática em um número inteiro, bem como a inversa. Por uma questão de simplicidade, assumimos que os números são representados por tipos long long e unsigned long long , estes são tipos de grande capacidade, ou seja, são adequados para a maioria dos casos.


Converter um número em uma sequência estática


Para converter um número em uma sequência estática, precisamos obter todos os dígitos do número, convertê-los nos caracteres correspondentes e criar uma sequência desses caracteres.


Para obter todos os dígitos de um número, usaremos um gerador semelhante ao gerador de sequência de índice. Defina uma sequência de caracteres:


 template<char ... Chars> struct char_sequence {}; 

Implementamos um gerador de caracteres de dígito, armazenando o número atual no primeiro parâmetro e os números a seguir, o próximo dígito é adicionado ao início da sequência e o número é dividido por dez:


 template<unsigned long long Value, char ... Chars> struct make_unsigned_int_char_sequence : make_unsigned_int_char_sequence<Value / 10, '0' + Value % 10, Chars ...> {}; 

Se o número atual for 0, então o descartamos, retornando uma sequência de dígitos, não há mais nada para converter:


 template<char ... Chars> struct make_unsigned_int_char_sequence<0, Chars ...> : char_sequence<Chars ...> {}; 

Você também deve levar em consideração o caso em que o número inicial é zero; nesse caso, você precisa retornar um caractere nulo; caso contrário, zero será convertido em uma sequência de caracteres vazia e, em seguida, em uma string vazia:


 template<> struct make_unsigned_int_char_sequence<0> : char_sequence<'0'> {}; 

O gerador implementado funciona muito bem para números positivos, mas não é adequado para números negativos. Definimos um novo gerador adicionando mais um parâmetro de modelo ao início - o sinal do número convertido:


 template<bool Negative, long long Value, char ... Chars> struct make_signed_int_char_sequence {}; 

Processaremos o número como mostrado acima, mas levando em consideração o sinal:


 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 ...> {}; 

Há um ponto sutil aqui, preste atenção em -(Value % 10) . Aqui, você não pode -Value % 10 , pois o intervalo de números negativos é um número maior que o intervalo de positivos e o módulo de número mínimo fica fora do conjunto de valores válidos.


Descartamos o número após o processamento, se for negativo, adicione o símbolo de sinal de 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 ...> {}; 

Separadamente, cuidamos da conversão de zero:


 template<> struct make_signed_int_char_sequence<false, 0> : char_sequence<'0'> {}; 

Por fim, implementamos as funções de conversão:


 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>{}); } 

Converter string estática em número


Para converter uma sequência estática em um número, você precisa converter os caracteres em números e adicioná-los, multiplicando anteriormente as dezenas pela potência correspondente. Executamos todas as ações recursivamente, para uma string vazia retornamos zero:


 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 converter números assinados, observe que os números negativos começam com um sinal de 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); } 

Considerações sobre a usabilidade da biblioteca


Neste ponto, a biblioteca já pode ser totalmente usada, mas alguns pontos são inconvenientes. Neste capítulo, veremos como você pode tornar o uso da biblioteca mais conveniente.


Objeto de cadeia estática


Empacote a string e os métodos implementados em um objeto. Isso permitirá o uso de nomes de métodos mais curtos, além de implementar operadores de comparação:


 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 + "!"; // 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"); // message = "I have 5 apples and 7 oranges, so I have 12 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)); // message = "A rectangle with width 123456789 and height 987654321 has area 121932631112635269" 

 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)); // message == "The first quarter has ended with net loss of $200000" 

URL:


 constexpr auto url = make_static_string("http://www.server.com:8080"); constexpr auto p = url.find("://"); constexpr auto protocol = url.prefix<p>(); // protocol == "http" constexpr auto sockaddr = url.suffix<p + 3>(); constexpr auto hp = sockaddr.split<sockaddr.find(':')>(); constexpr auto host = hp.first; // host == "www.server.com" constexpr int port = SSTOI(hp.second); // port == 8080 

:


 constexpr auto str = make_static_string("Hello"); for (size_t i = str.begin(); i != str.end(); ++i) //  std::cout << str[i]; std::cout << std::endl; // Hello for (size_t i = str.rbegin(); i != str.rend(); --i) //  std::cout << str[i]; std::cout << std::endl; // olleH 

Referências


, , 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 { // ... std::array<const Char, Size> data; }; 

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

, , - .

Source: https://habr.com/ru/post/pt428846/


All Articles