Arbeiten mit Strings in der Kompilierungsphase in modernem C ++


Wenn Sie in C ++ programmieren, haben Sie sich wahrscheinlich gefragt, warum Sie zwei Zeichenfolgenliterale nicht vergleichen oder ihre Verkettung nicht durchführen können:


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

Wie sie jedoch sagen: "Es ist unmöglich, aber wenn Sie wirklich wollen, dann können Sie." Wir werden Stereotypen unter dem Schnitt und direkt in der Kompilierungsphase brechen.


Warum das alles gebraucht wird


In einem der Projekte, an denen ich arbeitete, war es üblich, std :: string als String-Konstanten zu verwenden. Das Projekt hatte mehrere Module, in denen globale String-Konstanten definiert wurden:


 // 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"; // ... 

Ich denke, Sie haben bereits erraten, was eines Tages passiert ist. SAMPLE_PLUGIN_PATH hat den Wert "sample.so" , obwohl PLUGIN_PATH den Wert "/usr/local/lib/project/plugins/" hatte. Wie konnte das passieren? Alles ist sehr einfach, die Reihenfolge der Initialisierung globaler Objekte ist nicht definiert, zum Zeitpunkt der Initialisierung SAMPLE_PLUGIN_PATH Variable PLUGIN_PATH leer.


Darüber hinaus weist dieser Ansatz eine Reihe von Nachteilen auf. Erstens wird die beim Erstellen eines globalen Objekts ausgelöste Ausnahme nicht abgefangen. Zweitens erfolgt die Initialisierung während der Programmausführung, was wertvolle Prozessorzeit kostet.


Damals hatte ich die Idee, in der Kompilierungsphase mit Strings zu arbeiten, was schließlich zum Schreiben dieses Artikels führte.


In diesem Artikel werden Zeilen betrachtet, die in der Kompilierungsphase bearbeitet werden können. Wir werden solche Zeilen als statisch bezeichnen.


Alle implementierten Operationen wurden in die Bibliothek aufgenommen, um mit statischen Zeichenfolgen zu arbeiten. Der Quellcode der Bibliothek ist auf github verfügbar, der Link befindet sich am Ende des Artikels.


Für die Verwendung der Bibliothek ist mindestens C ++ 14 erforderlich.


Statische Zeichenfolgendefinition


Wir definieren eine statische Zeichenfolge als ein Array von Zeichen. Der Einfachheit halber nehmen wir an, dass die Zeichenfolge immer mit einem Nullzeichen endet:


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

Hier können Sie einen anderen Weg gehen und die Zeichenfolge als Tupel von Zeichen definieren. Diese Option erschien mir zeitaufwändiger und weniger bequem. Daher wird hier nicht darauf eingegangen.


Erstellen einer statischen Zeichenfolge


Schauen Sie sich die Definition der Hallo-Zeile oben an, es ist einfach schrecklich. Zuerst müssen wir die Länge des Arrays vorberechnen. Zweitens müssen Sie daran denken, das Nullzeichen am Ende zu schreiben. Drittens alle diese Kommas, Klammern und Anführungszeichen. Hier gibt es definitiv etwas zu tun. Ich möchte so etwas schreiben:


 constexpr auto hello = make_static_string("hello"); 

Hier hilft uns eine der Formen der Variablenvorlage, mit der wir die Vorlagenargumente als Indizes für die aggregierte Initialisierung unserer statischen Zeichenfolge aus einem Zeichenfolgenliteral erweitern können:


 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" 

Schon besser, aber Indizes müssen noch von Hand geschrieben werden. Hier stellen wir auch fest, dass Sie, wenn Sie nicht alle Indizes angeben, eine Teilzeichenfolge eines Zeichenfolgenliteral erhalten können. Wenn Sie sie in umgekehrter Reihenfolge schreiben, ist dies umgekehrt:


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

Diese Überlegung wird uns in Zukunft sehr nützlich sein.


Jetzt müssen wir irgendwie eine Folge von Zeilenindizes erzeugen. Wenden Sie dazu den Vererbungstrick an. Definieren Sie eine leere Struktur (Sie müssen etwas erben) mit einer Reihe erforderlicher Indizes als Vorlagenparameter:


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

Wir definieren eine Generatorstruktur, die nacheinander Indizes generiert und den Zähler im ersten Parameter speichert:


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

Wir kümmern uns auch um den Rekursionsendpunkt. Wenn alle Indizes generiert werden (der Zähler ist Null), verwerfen wir den Zähler und der Generator dreht sich in die Reihenfolge, die wir benötigen:


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

Infolgedessen sieht die Funktion zum Erstellen einer statischen Zeichenfolge folgendermaßen aus:


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

Wir werden eine ähnliche Funktion für eine statische Zeichenfolge schreiben, die für uns weiter nützlich sein wird:


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

Außerdem schreiben wir für jede Funktion, die das String-Literal foo(const char (& str)[Size]) , eine ähnliche Funktion, die den statischen String foo(const static_string<Size>& str) . Der Kürze halber werde ich dies jedoch nicht erwähnen.


Da uns die Länge des String-Literal bekannt ist, können wir automatisch eine Folge von Indizes generieren. Wir schreiben einen Wrapper für die obige Funktion:


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

Mit dieser Funktion können wir genau das tun, was wir zu Beginn des Kapitels wollten.


Wenn keine Argumente vorhanden sind, geben wir eine leere statische Zeichenfolge zurück, die nur aus einem Nullzeichen besteht:


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

Wir müssen auch eine Zeichenfolge aus einem Tupel von Zeichen erstellen:


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

Übrigens basiert alles, was später in diesem Artikel beschrieben wird, auf den in diesem Kapitel beschriebenen Techniken. Wenn etwas unverständlich bleibt, ist es daher besser, das Kapitel erneut zu lesen.


Geben Sie eine statische Zeichenfolge in einen Stream aus


Hier ist alles einfach. Da unsere Zeichenfolge mit einem Nullzeichen endet, reicht es aus, die Array-Daten an den Stream auszugeben:


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

Konvertieren Sie eine statische Zeichenfolge in eine std :: Zeichenfolge


Auch hier nichts Kompliziertes. Wir initialisieren den String mit Array-Daten:


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

Vergleich statischer Zeichenfolgen


Wir werden die Zeilen Zeichen für Zeichen vergleichen, bis wir die Unterschiede finden oder bis wir das Ende mindestens einer der Zeilen erreichen. Da constexpr for noch nicht erfunden wurde, verwenden wir die Rekursion und den ternären Operator:


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

In Zukunft werden wir eine erweiterte Version des Komparators benötigen, wir werden einen individuellen Index für jede ihrer Zeilen einführen und wir werden auch die Anzahl der verglichenen Zeichen begrenzen:


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

Mit dieser Version des Komparators können wir nicht nur den gesamten String, sondern auch einzelne Teilzeichenfolgen vergleichen.


Verkettung statischer Strings


Für die Verkettung verwenden wir dieselbe Variablenvorlage wie im Kapitel zum Erstellen einer statischen Zeichenfolge. Initialisieren Sie das Array zuerst mit den Zeichen der ersten Zeile (ohne das Nullzeichen), dann mit der zweiten und fügen Sie schließlich das Nullzeichen am Ende hinzu:


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

Wir implementieren auch eine Variablenvorlage zum Verketten einer beliebigen Anzahl von Zeichenfolgen oder Zeichenfolgenliteralen:


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

Suchvorgänge für statische Zeichenfolgen


Betrachten Sie die Operation zum Suchen eines Zeichens und einer Teilzeichenfolge in einer statischen Zeichenfolge.


Suchen Sie nach einem Zeichen in einer statischen Zeichenfolge


Die Zeichensuche ist nicht besonders schwierig. Überprüfen Sie die Zeichen rekursiv auf alle Indizes und geben Sie im Falle einer Übereinstimmung den ersten Index zurück. Wir geben auch die Möglichkeit, die Anfangsposition der Suche und die Sequenznummer der Übereinstimmung festzulegen:


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

Die Konstante static_string_npos zeigt an, dass die Suche nicht erfolgreich war. Wir definieren es wie folgt:


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

Ebenso implementieren wir eine Suche in die entgegengesetzte Richtung:


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

Bestimmen des Auftretens eines Zeichens in einer statischen Zeichenfolge


Um das Auftreten eines Charakters zu bestimmen, suchen Sie einfach danach:


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

Zählen Sie die Anzahl der Vorkommen eines Zeichens in einer statischen Zeichenfolge


Das Zählen der Anzahl der Vorkommen ist trivial implementiert:


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

Suchen Sie nach einer Teilzeichenfolge in einer statischen Zeichenfolge


Da davon ausgegangen wird, dass die statischen Zeilen relativ klein sind, werden wir den Knut-Morris-Pratt-Algorithmus hier nicht implementieren, sondern den einfachsten quadratischen Algorithmus implementieren:


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

Ebenso implementieren wir eine Suche in die entgegengesetzte Richtung:


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

Bestimmen des Auftretens eines Teilstrings in einer statischen Zeichenfolge


Versuchen Sie einfach, nach dem Auftreten eines Teilstrings zu suchen:


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

Bestimmen, ob eine statische Zeichenfolge mit / auf einem bestimmten Teilstring beginnt / endet


Mit dem zuvor beschriebenen Komparator können wir feststellen, ob die statische Zeichenfolge mit der angegebenen Teilzeichenfolge beginnt:


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

Ähnliches gilt für das Beenden einer statischen Linie:


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

Arbeiten mit statischen String-Teilzeichenfolgen


Hier betrachten wir die Operationen, die den Teilzeichenfolgen einer statischen Zeichenfolge zugeordnet sind.


Teilzeichenfolge, Präfix und Suffix einer statischen Zeichenfolge abrufen


Wie bereits erwähnt, müssen Sie zum Erhalten eines Teilstrings eine Folge von Indizes mit den angegebenen Start- und Endindizes generieren:


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

Wir implementieren das Erhalten eines Teilstrings, indem wir den Anfang und das Ende eines Teilstrings mit 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>{}); } 

Das Präfix ist eine Teilzeichenfolge, deren Anfang mit dem Anfang der ursprünglichen statischen Linie übereinstimmt:


 template<size_t End, size_t Size> constexpr auto static_string_prefix(const static_string<Size>& str) { return static_string_substring<0, End>(str); } 

In ähnlicher Weise stimmt für das Suffix nur das Ende überein:


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

Aufteilen einer statischen Zeichenfolge in zwei Teile an einem bestimmten Index


Um eine statische Zeichenfolge an einem bestimmten Index zu teilen, reicht es aus, das Präfix und das Suffix zurückzugeben:


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

Umkehrung der statischen Zeichenfolge


Um eine statische Zeichenfolge umzukehren, schreiben wir einen Indexgenerator, der Indizes in umgekehrter Reihenfolge generiert:


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

Jetzt implementieren wir eine Funktion, die die statische Zeichenfolge umkehrt:


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

Statische String-Hash-Berechnung


Wir werden den Hash mit der folgenden Formel berechnen:


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

Konvertieren Sie eine Zahl in eine statische Zeichenfolge und umgekehrt


In diesem Kapitel betrachten wir die Konvertierung einer statischen Zeichenfolge in eine Ganzzahl sowie die Umkehrung. Der Einfachheit halber nehmen wir an, dass Zahlen durch Typen mit long long und unsigned long long werden. Dies sind Typen mit großer Kapazität, dh sie sind für die meisten Fälle geeignet.


Konvertieren Sie eine Zahl in eine statische Zeichenfolge


Um eine Zahl in eine statische Zeichenfolge umzuwandeln, müssen wir alle Ziffern der Zahl abrufen, sie in die entsprechenden Zeichen konvertieren und aus diesen Zeichen eine Zeichenfolge erstellen.


Um alle Ziffern einer Zahl zu erhalten, verwenden wir einen Generator ähnlich dem Indexsequenzgenerator. Definieren Sie eine Zeichenfolge:


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

Wir implementieren einen Ziffernzeichengenerator, der die aktuelle Nummer im ersten Parameter und die Zahlen im Folgenden speichert. Die nächste Ziffer wird am Anfang der Sequenz hinzugefügt und die Nummer wird durch zehn geteilt:


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

Wenn die aktuelle Zahl 0 ist, verwerfen wir sie und geben eine Folge von Ziffern zurück. Es gibt nichts mehr zu konvertieren:


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

Sie sollten auch den Fall berücksichtigen, in dem die Anfangszahl Null ist. In diesem Fall müssen Sie ein Nullzeichen zurückgeben. Andernfalls wird Null in eine leere Zeichenfolge und dann in eine leere Zeichenfolge konvertiert:


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

Der implementierte Generator eignet sich hervorragend für positive Zahlen, ist jedoch nicht für negative Zahlen geeignet. Wir definieren einen neuen Generator, indem wir am Anfang einen weiteren Vorlagenparameter hinzufügen - das Vorzeichen der konvertierten Zahl:


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

Wir werden die Nummer wie oben gezeigt verarbeiten, aber das Zeichen berücksichtigen:


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

Es gibt hier einen subtilen Punkt, achten Sie auf -(Value % 10) . Hier können -Value % 10 nicht -Value % 10 , da der Bereich der negativen Zahlen eine Zahl größer ist als der Bereich der positiven Zahlen und der Mindestzahlmodul aus dem Satz gültiger Werte herausfällt.


Wir verwerfen die Zahl nach der Verarbeitung. Wenn sie negativ ist, fügen Sie das Minuszeichen hinzu:


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

Separat kümmern wir uns um die Konvertierung von Null:


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

Schließlich implementieren wir die Konvertierungsfunktionen:


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

Konvertieren Sie eine statische Zeichenfolge in eine Zahl


Um eine statische Zeichenfolge in eine Zahl umzuwandeln, müssen Sie die Zeichen in Zahlen umwandeln und diese dann hinzufügen, nachdem Sie zuvor zehn mit der entsprechenden Potenz multipliziert haben. Wir führen alle Aktionen rekursiv aus. Für eine leere Zeichenfolge geben wir Null zurück:


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

Beachten Sie beim Konvertieren von vorzeichenbehafteten Zahlen, dass negative Zahlen mit einem Minuszeichen beginnen:


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

Überlegungen zur Benutzerfreundlichkeit der Bibliothek


Zu diesem Zeitpunkt ist es bereits möglich, die Bibliothek vollständig zu nutzen, einige Punkte sind jedoch unpraktisch. In diesem Kapitel erfahren Sie, wie Sie die Verwendung der Bibliothek komfortabler gestalten können.


Statisches String-Objekt


Packen Sie den String und die implementierten Methoden in ein Objekt. Dies ermöglicht die Verwendung kürzerer Methodennamen sowie die Implementierung von Vergleichsoperatoren:


 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 

Referenzen


, , 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/de428846/


All Articles