
Si vous programmez en C ++, vous vous êtes probablement demandé pourquoi vous ne pouvez pas comparer deux littéraux de chaîne ou effectuer leur concaténation:
auto str = "hello" + "world";
Cependant, comme on dit, "c'est impossible, mais si vous le voulez vraiment, alors vous pouvez." Nous briserons les stéréotypes sous la coupe et dès la compilation.
Pourquoi tout cela est nécessaire
Dans l'un des projets sur lesquels je travaillais, il était habituel d'utiliser std :: string comme constantes de chaîne. Le projet comportait plusieurs modules dans lesquels des constantes de chaîne globales étaient définies:
Je pense que vous avez déjà deviné ce qui s'est passé un jour. SAMPLE_PLUGIN_PATH
pris la valeur "sample.so"
, malgré le fait que PLUGIN_PATH
avait la valeur "/usr/local/lib/project/plugins/"
, comme prévu. Comment cela a-t-il pu arriver? Tout est très simple, l'ordre d'initialisation des objets globaux n'est pas défini, au moment de l'initialisation SAMPLE_PLUGIN_PATH
variable PLUGIN_PATH
était vide.
De plus, cette approche présente un certain nombre d'inconvénients. Tout d'abord, l'exception levée lors de la création d'un objet global n'est pas interceptée. Deuxièmement, l'initialisation se produit pendant l'exécution du programme, ce qui passe un temps processeur précieux.
C'est alors que j'ai eu l'idée de travailler avec des cordes au stade de la compilation, ce qui a finalement conduit à la rédaction de cet article.
Dans cet article, nous considérons les lignes qui peuvent être utilisées au stade de la compilation. Nous appellerons ces lignes statiques.
Toutes les opérations implémentées ont été incluses dans la bibliothèque pour travailler avec des chaînes statiques. Le code source de la bibliothèque est disponible sur github, le lien se trouve à la fin de l'article.
Au moins C ++ 14 est requis pour utiliser la bibliothèque.
Définition de chaîne statique
Nous définissons une chaîne statique comme un tableau de caractères, pour plus de commodité, nous supposons que la chaîne se termine toujours par un caractère nul:
template<size_t Size> using static_string = std::array<const char, Size>; constexpr static_string<6> hello = {'H', 'e', 'l', 'l', 'o', '\0'};
Ici, vous pouvez procéder différemment et définir la chaîne comme un tuple de caractères. Cette option me semblait plus longue et moins pratique. Par conséquent, il ne sera pas considéré ici.
Création d'une chaîne statique
Regardez la définition de la ligne bonjour ci-dessus, c'est tout simplement horrible. Tout d'abord, nous devons pré-calculer la longueur du tableau. Deuxièmement, vous devez vous rappeler d'écrire le caractère nul à la fin. Troisièmement, toutes ces virgules, crochets et guillemets. Il y a certainement quelque chose à faire à ce sujet. Je voudrais écrire quelque chose comme ceci:
constexpr auto hello = make_static_string("hello");
Ici, l'une des formes du modèle de variable nous aidera, ce qui nous permet d'étendre les arguments du modèle en tant qu'indices pour l'initialisation agrégée de notre chaîne statique à partir d'un littéral de chaîne:
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");
Déjà mieux, mais les index doivent encore être écrits à la main. Ici, nous notons également que si vous ne spécifiez pas tous les indices, vous pouvez obtenir une sous-chaîne d'un littéral de chaîne, et si vous les écrivez dans l'ordre inverse, alors son inverse:
constexpr hello1 = make_static_string<1, 2, 3>("hello");
Cette considération nous sera très utile à l'avenir.
Maintenant, nous devons en quelque sorte générer une séquence d'indices de ligne. Pour ce faire, appliquez l'astuce d'héritage. Définissez une structure vide (vous devez hériter de quelque chose) avec un ensemble d'indices requis comme paramètres de modèle:
template<size_t ... Indexes> struct index_sequence {};
Nous définissons une structure de générateur qui va générer des index un par un, en stockant le compteur dans le premier paramètre:
template<size_t Size, size_t ... Indexes> struct make_index_sequence : make_index_sequence<Size - 1, Size - 1, Indexes ...> {};
Nous nous occuperons également du point final de récursivité, lorsque tous les indices sont générés (le compteur est nul), nous rejetons le compteur et le générateur se transforme en la séquence dont nous avons besoin:
template<size_t ... Indexes> struct make_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {};
Par conséquent, la fonction de création d'une chaîne statique ressemblera à ceci:
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'}; }
Nous allons écrire une fonction similaire pour une chaîne statique, elle nous sera utile plus loin:
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'}; }
De plus, pour chaque fonction qui prend la chaîne littérale foo(const char (& str)[Size])
nous allons écrire une fonction similaire qui prend la chaîne statique foo(const static_string<Size>& str)
. Mais pour être bref, je ne le mentionnerai pas.
Puisque la longueur du littéral de chaîne nous est connue, nous pouvons générer automatiquement une séquence d'indices, nous allons écrire un wrapper pour la fonction ci-dessus:
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>{}); }
Cette fonction nous permet de faire exactement ce que nous voulions au début du chapitre.
S'il n'y a pas d'arguments, nous retournerons une chaîne statique vide, qui se compose uniquement d'un caractère nul:
constexpr static_string<1> make_static_string() { return {'\0'}; }
Nous devons également créer une chaîne à partir d'un tuple de caractères:
template<char ... Chars> constexpr static_string<sizeof ... (Chars) + 1> make_static_string(char_sequence<Chars ...>) { return {Chars ..., '\0'}; }
Soit dit en passant, tout ce qui sera décrit plus loin dans cet article est basé sur les techniques décrites dans ce chapitre. Par conséquent, si quelque chose reste incompréhensible, il est préférable de relire le chapitre à nouveau.
Sortie d'une chaîne statique dans un flux
Ici, tout est simple. Puisque notre chaîne se termine par un caractère nul, il suffit de sortir les données du tableau dans le flux:
template<size_t Size> std::ostream& operator<<(std::ostream& os, const static_string<Size>& str) { os << str.data(); return os; }
Convertir une chaîne statique en std :: string
Rien de compliqué ici non plus. Nous initialisons la chaîne avec les données du tableau:
template<size_t Size> std::string to_string(const static_string<Size>& str) { return std::string(str.data()); }
Comparaison de chaînes statiques
Nous comparerons les lignes caractère par caractère jusqu'à ce que nous trouvions les différences, ou jusqu'à la fin d'au moins une des lignes. Comme constexpr for n'a pas encore été inventé, nous utilisons la récursivité et l'opérateur ternaire:
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); }
À l'avenir, nous aurons besoin d'une version étendue du comparateur, nous introduirons un index individuel pour chacune de leurs lignes, et nous limiterons également le nombre de caractères comparés:
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); }
Cette version du comparateur nous permettra de comparer non seulement la chaîne entière, mais aussi des sous-chaînes individuelles.
Concaténation de chaînes statiques
Pour la concaténation, nous utilisons le même modèle de variable que dans le chapitre sur la création d'une chaîne statique. Initialisez d'abord le tableau avec les caractères de la première ligne (à l'exclusion du caractère nul), puis le second, et enfin ajoutez le caractère nul à la fin:
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>{}); }
Nous implémentons également un modèle de variable pour concaténer un nombre arbitraire de chaînes ou de littéraux de chaîne:
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) ...)); }
Opérations de recherche de chaînes statiques
Considérez l'opération de recherche d'un caractère et d'une sous-chaîne dans une chaîne statique.
Rechercher un caractère dans une chaîne statique
La recherche de caractères n'est pas particulièrement difficile, vérifiez récursivement les caractères pour tous les index et retournez le premier index en cas de correspondance. Nous donnerons également la possibilité de définir la position initiale de la recherche et le numéro de séquence du match:
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
indique que la recherche a échoué. Nous le définissons comme suit:
constexpr size_t static_string_npos = std::numeric_limits<size_t>::max();
De même, nous implémentons une recherche en sens inverse:
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; }
Déterminer l'occurrence d'un caractère dans une chaîne statique
Pour déterminer l'occurrence d'un personnage, essayez simplement de le rechercher:
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; }
Compter le nombre d'occurrences d'un caractère dans une chaîne statique
Le comptage du nombre d'occurrences est implémenté de manière triviale:
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); }
Rechercher une sous-chaîne dans une chaîne statique
Puisqu'on suppose que les lignes statiques seront relativement petites, nous n'implémenterons pas l'algorithme Knut-Morris-Pratt ici, nous implémenterons l'algorithme quadratique le plus 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; }
De même, nous implémentons une recherche en sens inverse:
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; }
Déterminer l'occurrence d'une sous-chaîne dans une chaîne statique
Pour déterminer l'occurrence d'une sous-chaîne, essayez simplement de la rechercher:
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; }
Déterminer si une chaîne statique commence / finit par / sur une sous-chaîne donnée
En appliquant le comparateur décrit précédemment, nous pouvons déterminer si la chaîne statique commence par la sous-chaîne donnée:
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; }
De même pour terminer une ligne statique:
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; }
Utilisation de sous-chaînes de chaînes statiques
Nous regardons ici les opérations associées aux sous-chaînes d'une chaîne statique.
Obtention de la sous-chaîne, du préfixe et du suffixe d'une chaîne statique
Comme nous l'avons noté précédemment, pour obtenir une sous-chaîne, vous devez générer une séquence d'indices, avec les indices de début et de fin donnés:
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 ...> {};
Nous implémentons l'obtention d'une sous-chaîne en vérifiant le début et la fin d'une sous-chaîne en utilisant 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>{}); }
Le préfixe est une sous-chaîne dont le début coïncide avec le début de la ligne statique d'origine:
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 même pour le suffixe, seule la fin correspond:
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); }
Fractionner une chaîne statique en deux parties à un indice donné
Pour diviser une chaîne statique à un index donné, il suffit de renvoyer le préfixe et le suffixe:
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)); }
Inversion de chaîne statique
Pour inverser une chaîne statique, nous écrivons un générateur d'index qui génère des index dans l'ordre inverse:
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 ...> {};
Maintenant, nous implémentons une fonction qui inverse la chaîne statique:
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>{}); }
Calcul de hachage de chaîne statique
Nous calculerons le hachage en utilisant la formule suivante:
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; }
Convertir un nombre en une chaîne statique et vice versa
Dans ce chapitre, nous examinons la conversion d'une chaîne statique en entier, ainsi que l'inverse. Pour simplifier, nous supposons que les nombres sont représentés par des types long long
et unsigned long long
, ce sont des types de grande capacité, c'est-à-dire qu'ils conviennent à la plupart des cas.
Convertir un nombre en chaîne statique
Pour convertir un nombre en une chaîne statique, nous devons obtenir tous les chiffres du nombre, les convertir en caractères correspondants et créer une chaîne de ces caractères.
Pour obtenir tous les chiffres d'un nombre, nous utiliserons un générateur similaire au générateur de séquence d'index. Définissez une séquence de caractères:
template<char ... Chars> struct char_sequence {};
Nous implémentons un générateur de caractères numériques, stockant le numéro actuel dans le premier paramètre et les chiffres suivants, le chiffre suivant est ajouté au début de la séquence et le nombre est divisé par dix:
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 le nombre actuel est 0, alors nous le rejetons, en retournant une séquence de chiffres, il n'y a plus rien à convertir:
template<char ... Chars> struct make_unsigned_int_char_sequence<0, Chars ...> : char_sequence<Chars ...> {};
Vous devriez également considérer le cas où le nombre initial est zéro, dans ce cas, vous devez retourner un caractère nul, sinon zéro sera converti en une séquence de caractères vide, puis en une chaîne vide:
template<> struct make_unsigned_int_char_sequence<0> : char_sequence<'0'> {};
Le générateur implémenté fonctionne très bien pour les nombres positifs, mais ne convient pas aux nombres négatifs. Nous définissons un nouveau générateur en ajoutant un paramètre de modèle supplémentaire au début - le signe du nombre converti:
template<bool Negative, long long Value, char ... Chars> struct make_signed_int_char_sequence {};
Nous traiterons le numéro comme indiqué ci-dessus, mais en tenant compte du signe:
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 ...> {};
Il y a un point subtil ici, faites attention à -(Value % 10)
. Ici, vous ne pouvez pas -Value % 10
, car la plage de nombres négatifs est un nombre plus large que la plage de positifs et le module numérique minimum tombe hors de l'ensemble des valeurs valides.
Nous éliminons le nombre après le traitement, s'il est négatif, ajoutez le symbole du signe moins:
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 ...> {};
Séparément, nous nous occupons de convertir zéro:
template<> struct make_signed_int_char_sequence<false, 0> : char_sequence<'0'> {};
Enfin, nous implémentons les fonctions de conversion:
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 une chaîne statique en nombre
Pour convertir une chaîne statique en nombre, vous devez convertir les caractères en nombres, puis les ajouter, après avoir multiplié les dizaines par la puissance correspondante. Nous effectuons toutes les actions récursivement, pour une chaîne vide nous retournons zéro:
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); }
Pour convertir des nombres signés, notez que les nombres négatifs commencent par un signe moins:
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); }
Considérations d'utilisation de la bibliothèque
À ce stade, il est déjà possible d'utiliser pleinement la bibliothèque, mais certains points ne sont pas pratiques. Dans ce chapitre, nous verrons comment vous pouvez rendre l'utilisation de la bibliothèque plus pratique.
Objet chaîne statique
Emballez la chaîne et les méthodes implémentées dans un objet. Cela permettra d'utiliser des noms de méthode plus courts, ainsi que d'implémenter des opérateurs de comparaison:
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)
Les références
, , 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; }
, , - .