Working with strings at compile time in modern C ++


    If you are programming in C ++, you probably wondered why you cannot compare two string literals or perform their concatenation:


    auto str = "hello" + "world"; // ошибка компиляцииif ("hello" < "world") { // компилируется, но работает не так, как ожидалось// ...
    }

    However, as they say, "it is impossible, but if you really want it, you can." We will break stereotypes under the cut, and right at the compilation stage.


    Why is all this necessary


    In one of the projects I was working on, it was decided to use std :: string as string constants. There were several modules in the project in which global string constants were defined:


    // plugin.hconststd::string PLUGIN_PATH = "/usr/local/lib/project/plugins/";
    // ...

    // sample_plugin.hconststd::string SAMPLE_PLUGIN_LIB = PLUGIN_PATH + "sample.so";
    // ...

    I think you already guessed what happened one day. SAMPLE_PLUGIN_PATHtook on the value "sample.so", even though it PLUGIN_PATHmattered "/usr/local/lib/project/plugins/", as expected. How could this happen? It's very simple, the order of initialization of global objects is not defined, at the time of initialization the SAMPLE_PLUGIN_PATHvariable PLUGIN_PATHwas empty.


    In addition, this approach has a number of disadvantages. First, the exception thrown when creating a global object is not caught. Secondly, initialization occurs during program execution, which is wasting precious processor time.


    It was then that I had the idea of ​​working with strings at the compilation stage, which eventually led to the writing of this article.


    In this article, we consider the lines that can be operated on at the compilation stage. Let's call these strings static.


    All implemented operations were included in the library for working with static strings. Library source code is available on github, link at the end of the article.


    To use the library requires at least C ++ 14.


    Static string definition


    We define a static string as an array of characters, for convenience, we assume that a string always ends with a null character:


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

    Here you can go the other way, and define the string as a tuple of characters. This option seemed to me more laborious and less convenient. Therefore, it will not be considered here.


    Creating a static string


    Look at the definition of the string hello above, it is just awful. First, we need to calculate the length of the array in advance. Secondly, you need to remember to write the zero character at the end. Thirdly, all these commas, brackets and quotes. Definitely, something needs to be done about it. I would like to write something like this:


    constexprauto hello = make_static_string("hello");

    Here we are helped by one of the forms of the variable template, which allows us to expand the template arguments as indices for the aggregate initialization of our static string from a string literal:


    template<size_t Size, size_t ... Indexes>
    constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(constchar (& str)[Size]) {
        return {str[Indexes] ..., '\0'};
    }
    constexprauto hello = make_static_string<0, 1, 2, 3, 4>("hello"); // hello == "hello"

    Already better, but still have to write indices by hand. Here we also note that if you do not specify all the indices, then you can get a substring of a string literal, and if you write them in reverse order, then its inverse:


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

    This consideration is very useful to us in the future.


    Now we need to somehow generate a sequence of row indices. For this, we apply the inheritance trick. We define an empty structure (you need to inherit something) with a set of required indexes as template parameters:


    template<size_t ... Indexes>
    structindex_sequence {};

    We define a generator structure that will generate indices one by one, storing the counter in the first parameter:


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

    We also take care of the end point of the recursion, when all indices are generated (the counter is zero), we discard the counter and the generator turns into the necessary sequence:


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

    As a result, the function for creating a static string will look like this:


    template<size_t Size, size_t ... Indexes>
    constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(constchar (& str)[Size],
        index_sequence<Indexes ...>) {
            return {str[Indexes] ..., '\0'};
    }

    Let's write a similar function for a static string, it will be useful to us further:


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

    Further, for each function that accepts a string literal, foo(const char (& str)[Size])we will write a similar function that accepts a static string foo(const static_string<Size>& str). But I, for brevity, will not mention this.


    Since the length of the string literal is known to us, we can automatically generate a sequence of indices, we will write a wrapper for the function above:


    template<size_t Size>
    constexpr static_string<Size> make_static_string(constchar (& str)[Size]) {
        return make_static_string(str, make_index_sequence<Size - 1>{});
    }

    This feature allows you to do exactly what we wanted at the beginning of the chapter.


    If there are no arguments, we will return an empty static string, which consists only of the null character:


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

    We also need to create a string from a tuple of characters:


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

    By the way, everything that will be described later in this article is based on the techniques described in this chapter. Therefore, if something remains unclear, it is better to re-read the chapter again.


    Output a static string to a stream


    Everything is simple here. Since our string ends with a null character, it suffices to output the array data to the stream:


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

    Convert static string to std :: string


    Here, too, nothing complicated. Initialize the string with the array data:


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

    Static string comparison


    We will compare strings character by character until we find differences, or reach the end of at least one of the strings. Since constexpr for has not yet been invented, we will use recursion and the ternary operator:


    template<size_t Size1, size_t Size2>
    constexprintstatic_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 the future, we need an extended version of the comparator, we introduce an individual index for each of their rows, and we also limit the number of compared characters:


    template<size_t Size1, size_t Size2>
    constexprintstatic_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);
    }

    This version of the comparator will allow us to compare not only strings entirely, but also individual substrings.


    Static string concatenation


    For concatenation we use the same variable pattern as in the chapter about creating a static string. We first initialize the array with the characters of the first line (excluding the null character), then the second, and finally add the null character to the end:


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

    We also implement a variable pattern for concatenating an arbitrary number of strings or string literals:


    constexprautostatic_string_concat(){
        return make_static_string();
    }
    template<typename Arg, typename ... Args>
    constexprautostatic_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) ...));
    }

    Static String Search Operations


    Consider searching for a character and a substring in a static string.


    Search for a character in a static string


    Finding a character is not particularly difficult, recursively checking characters for all indexes and returning the first index in case of a match. We will also give the opportunity to set the initial search position and the sequence number of a 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;
    }

    The constant static_string_nposindicates that the search did not succeed. We define it as follows:


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

    Similarly, we implement a search in the opposite direction:


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

    Determining whether a character is in a static string


    To determine the occurrence of a character, try searching for it:


    template<size_t Size>
    constexprboolstatic_string_contains(const static_string<Size>& str, char ch){
        return static_string_find(str, ch) != static_string_npos;
    }

    Counting the number of occurrences of a character in a static string


    Counting the number of occurrences is trivially implemented:


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

    Search for a substring in a static string


    Since it is assumed that static strings will be relatively small, we will not implement the Knut-Morris-Pratt algorithm here, we will implement the simplest quadratic algorithm:


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

    Similarly, we implement a search in the opposite direction:


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

    Determining whether a substring is in a static string


    To determine the occurrence of a substring, just try to search for it:


    template<size_t Size, size_t SubSize>
    constexprboolstatic_string_contains(const static_string<Size>& str, const static_string<SubSize>& substr){
        return static_string_find(str, substr) != static_string_npos;
    }

    Determining whether a static string begins / ends with / on a given substring


    Using the previously described comparator, we can determine whether a static string begins with a given substring:


    template<size_t SubSize, size_t Size>
    constexprboolstatic_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;
    }

    Similarly, to end a static line:


    template<size_t SubSize, size_t Size>
    constexprboolstatic_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;
    }

    Work with static line spacing


    Here we look at the operations associated with spacing static strings.


    Getting the substring, prefix and suffix of a static string


    As we noted earlier, to obtain a substring, you need to generate a sequence of indices, with a given initial and final indices:


    template<size_t Begin, size_t End, size_t ... Indexes>
    structmake_index_subsequence : make_index_subsequence<Begin, End - 1, End - 1, Indexes ...> {};
    template<size_t Pos, size_t ... Indexes>
    structmake_index_subsequence<Pos, Pos, Indexes ...> : index_sequence<Indexes ...> {};

    We implement getting a substring with checking the beginning and end of the substring using static_assert:


    template<size_t Begin, size_t End, size_t Size>
    constexprautostatic_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>{});
    }

    The prefix is ​​a substring whose beginning coincides with the beginning of the original static string:


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

    Similarly for a suffix, only the end matches:


    template<size_t Begin, size_t Size>
    constexprautostatic_string_suffix(const static_string<Size>& str){
        return static_string_substring<Begin, Size - 1>(str);
    }

    Splitting a static string into two parts at a given index


    To divide a static string by a given index, it is enough to return the prefix and suffix:


    template<size_t Index, size_t Size>
    constexprautostatic_string_split(const static_string<Size>& str){
        returnstd::make_pair(static_string_prefix<Index>(str), static_string_suffix<Index + 1>(str));
    }

    Reversing a static string


    To reverse a static string, we write an index generator, which generates indexes in the reverse order:


    template<size_t Size, size_t ... Indexes>
    structmake_reverse_index_sequence : make_reverse_index_sequence<Size - 1, Indexes ..., Size - 1> {};
    template<size_t ... Indexes>
    structmake_reverse_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {};

    Now we will implement a function that reverses the static string:


    template<size_t Size>
    constexprautostatic_string_reverse(const static_string<Size>& str){
        return make_static_string(str, make_reverse_index_sequence<Size - 1>{});
    }

    Static string hash calculation


    We will calculate the hash using the following formula:


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

    Convert number to static string and back


    In this chapter, we consider the conversion of a static string to an integer, as well as the inverse transformation. For simplicity, we assume that the numbers are represented by types long longand unsigned long long, these are types of large capacity, that is, they are suitable for most cases.


    Convert number to static string


    To convert a number to a static string, we need to get all the digits of the number, convert them to the corresponding characters, and make a string of these characters.


    To obtain all digits of a number we will use a generator similar to the generator of a sequence of indices. Define a sequence of characters:


    template<char ... Chars>
    structchar_sequence {};    

    We implement the character generator of numbers, storing the current number in the first parameter, and the numbers in the next, the next number is added to the beginning of the sequence, and the number is divided by ten:


    template<unsignedlonglong Value, char ... Chars>
    structmake_unsigned_int_char_sequence : make_unsigned_int_char_sequence<Value / 10, '0' + Value % 10, Chars ...> {};

    If the current number is 0, then discard it, returning a sequence of digits, there is nothing more to convert:


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

    It is also necessary to take into account the case when the initial number is zero, in this case it is necessary to return a null character, otherwise null will be converted to an empty sequence of characters, and then to an empty string:


    template<>
    structmake_unsigned_int_char_sequence<0> : char_sequence<'0'> {};

    The implemented generator works great for positive numbers, but is not suitable for negative ones. We define a new generator by adding one more template parameter to the beginning - the sign of the number to be converted:


    template<bool Negative, longlong Value, char ... Chars>
    structmake_signed_int_char_sequence {};

    We will process the number in the same way as shown above, but taking into account the sign:


    template<longlong Value, char ... Chars>
    structmake_signed_int_char_sequence<true, Value, Chars ...> :
        make_signed_int_char_sequence<true, Value / 10, '0' + -(Value % 10), Chars ...> {};
    template<longlong Value, char ... Chars>
    structmake_signed_int_char_sequence<false, Value, Chars ...> :
        make_signed_int_char_sequence<false, Value / 10, '0' + Value % 10, Chars ...> {};

    There is one subtle point here, pay attention to -(Value % 10). It is impossible here -Value % 10, since the range of negative numbers is one number wider than the range of positive numbers and the minimum number modulus falls out of the set of acceptable values.


    We discard the number after processing, if it is negative, add a minus sign symbol:


    template<char ... Chars>
    structmake_signed_int_char_sequence<true, 0, Chars ...> : char_sequence<'-', Chars ...> {};
    template<char ... Chars>
    structmake_signed_int_char_sequence<false, 0, Chars ...> : char_sequence<Chars ...> {};

    Separately, we take care of converting zero:


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

    Finally, we implement the conversion functions:


    template<unsignedlonglong Value>
    constexprautouint_to_static_string(){
        return make_static_string(make_unsigned_int_char_sequence<Value>{});
    }
    template<longlong Value>
    constexprautoint_to_static_string(){
        return make_static_string(make_signed_int_char_sequence<(Value < 0), Value>{});
    }

    Convert static string to number


    To convert a static string to a number, you need to convert the characters into numbers, and then add them together, multiplying the tens of them by the corresponding degree. We perform all actions recursively, for an empty string we return zero:


    template<size_t Size>
    constexprunsignedlonglongstatic_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>
    constexprunsignedlonglongstatic_string_to_uint(const static_string<Size>& str){
        return static_string_to_uint(str, Size - 2);
    }

    To convert signed numbers, you need to consider that negative numbers start with a minus sign:


    template<size_t Size>
    constexprlonglongstatic_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>
    constexprlonglongstatic_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); 
    }

    Library Usability Issues


    By this time the library is already possible to fully use, but some moments cause inconvenience. In this chapter, we will look at how to make using the library more convenient.


    Static line object


    We pack the string and the implemented methods into the object. This will allow the use of shorter method names, as well as implement comparison operators:


    template<size_t Size> structstatic_string {constexpr size_t length()const{
            return Size - 1;
        }
        constexpr size_t size()const{
            return Size;
        }
        constexpr size_t begin()const{
            return0;
        }
        constexpr size_t end()const{
            return Size - 1;
        }
        constexpr size_t rbegin()const{
            return Size - 2;
        }
        constexpr size_t rend()const{
            returnstd::numeric_limits<size_t>::max();
        }
        constexprboolempty()const{
            return Size < 2;
        }
        constexprautoreverse()const{
            return static_string_reverse(*this);
        }
        template<size_t Begin, size_t End> constexprautosubstring()const{
            return static_string_substring<Begin, End>(*this);
        }
        template<size_t End> constexprautoprefix()const{
            return static_string_prefix<End>(*this);
        }
        template<size_t Begin> constexprautosuffix()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(constchar (& 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(constchar (& substr)[SubSize], size_t from = Size - SubSize, size_t nth = 0)const{
            return static_string_rfind(*this, substr, from, nth);
        }
        constexprboolcontains(char ch)const{
            return static_string_contains(*this, ch);
        }
        template<size_t SubSize> constexprboolcontains(const static_string<SubSize>& substr)const{
            return static_string_contains(*this, substr);
        }
        template<size_t SubSize> constexprboolcontains(constchar (& substr)[SubSize])const{
            return static_string_contains(*this, substr);
        }
        template<size_t SubSize> constexprboolstarts_with(const static_string<SubSize>& prefix)const{
            return static_string_starts_with(*this, prefix);
        }
        template<size_t SubSize> constexprboolstarts_with(constchar (& prefix)[SubSize])const{
            return static_string_starts_with(*this, prefix);
        }
        template<size_t SubSize> constexprboolends_with(const static_string<SubSize>& suffix)const{
            return static_string_ends_with(*this, suffix);
        }
        template<size_t SubSize> constexprboolends_with(constchar (& 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> constexprautosplit()const{
            return static_string_split<Index>(*this);
        }
        constexprunsignedlonglonghash()const{
            return static_string_hash(*this);
        }
        constexprcharoperator[](size_t index) const {
            return data[index];
        }
        std::stringstr()const{
            return to_string(*this);
        }
        std::array<constchar, Size> data;
    };

    Comparison operators


    Using the comparator as a function is inconvenient and unreadable. Define global comparison operators:


    template<size_t Size1, size_t Size2>
    constexprbooloperator<(const static_string<Size1>& str1, const static_string<Size2>& str2) {
        return static_string_compare(str1, str2) < 0;
    }

    Similarly, we implement the remaining operators> <=> = ==! =, For all variations of the arguments of static strings and string literals. Here it makes no sense to bring them because of the triviality.


    Number macros


    For the convenience of converting a number to a static string and back, we define the appropriate macros:


    #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))

    Library Usage Examples


    Below are examples of real use of the implemented library.


    Concatenation of static strings and string literals:


    constexprauto hello = make_static_string("Hello");
    constexprauto world = make_static_string("World");
    constexprauto greeting = hello + ", " + world + "!"; // greeting == "Hello, World!"

    Concatenation of static strings, string literals and numbers:


    constexprint apples = 5;
    constexprint oranges = 7;
    constexprauto 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"    

    constexprunsignedlonglong width = 123456789ULL;
    constexprunsignedlonglong height = 987654321ULL;
    constexprauto 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"    

    constexprlonglong revenue = 1'000'000LL;
    constexprlonglong costs = 1'200'000LL;
    constexprlonglong profit = revenue - costs;
    constexprauto 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"

    Parsing URL:


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

    Iteration of characters in both directions:


    constexprauto str = make_static_string("Hello");
    for (size_t i = str.begin(); i != str.end(); ++i) // впередstd::cout << str[i];
    std::cout << std::endl; // Hellofor (size_t i = str.rbegin(); i != str.rend(); --i) // назадstd::cout << str[i];
    std::cout << std::endl; // olleH

    Links


    A library that implements all of the above can be found in my github


    Thank you for your attention, comments and additions are welcome.


    Update


    Implemented a custom _ss literal to create static strings from string literals:


    template<typename Char, Char ... Chars>
    constexpr basic_static_string<Char, sizeof ... (Chars) + 1> operator"" _ss() {
        return {Chars ..., static_cast<Char>('\0')};
    };

    The make_static_string () function was hidden in the internal non-space, everything began to look nicer:


    constexprauto hello_world = "Hello"_ss + " World";
    if ("Hello" < "World"_ss) { ... }
    constexprauto hash = "VeryLongString"_ss.hash();

    Added Char template parameter instead of char:


    template<typename Char, size_t Size> structbasic_static_string {// ...std::array<const Char, Size> data;
    };

    Made specialization for char and whar_t, the lower ones are used as namespaces to pull the static concat, which is introduced into the structure of the static string:


    template<size_t Size> usingstatic_string_t = basic_static_string<char, Size>;
    template<size_t Size> usingstatic_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>;

    Now everything works for "wide" literals:


    constexprauto wide_string = L"WideString"_ss;
    constexprint apples = 5;
    constexprint oranges = 7;
    constexprint fruits = apples + oranges;
    constexprauto 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;

    Corrected the size () method, now size () and length () return the length of the string without taking into account the null character, to get the size of the array, use sizeof ():


    constexprauto ss1 = "Hello"_ss;
    static_assert(ss1.length() == 5, "");
    static_assert(ss1.size() == 5, "");
    static_assert(sizeof(ss1) == 6, "");

    The updated version is on github
    Thank you all for the helpful comments.


    Update 2


    During the discussion with AndreySu , another way to implement static strings appeared, where characters are passed as template parameters:


    #include<iostream>usingnamespacestd;
    template<typename Char, Char ... Chars> structstatic_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 ... >{};
    };
    intmain(){
        constexprauto str1 = "abc"_ss;
        constexprauto str2 = "def"_ss;
        constexprauto str = str1 + str2 + str1;
        std::cout << str << std::endl;
        return0;
    }

    Detailed consideration of this option is beyond the scope of the article, I will be glad if someone from the readers will be engaged in bringing it to mind.


    Also popular now: