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.
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.h const std::string PLUGIN_PATH = "/usr/local/lib/project/plugins/"; // ...
// sample_plugin.h const std::string SAMPLE_PLUGIN_LIB = PLUGIN_PATH + "sample.so"; // ...
I think you already guessed what happened one day. SAMPLE_PLUGIN_PATH
to "sample.so"
, despite the fact that PLUGIN_PATH
was set to "/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 of SAMPLE_PLUGIN_PATH
variable PLUGIN_PATH
was 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 when the program is executed, which is wasting precious CPU 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 codes are available on github, link at the end of the article.
To use the library requires at least C ++ 14.
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<const char, 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.
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:
constexpr auto hello = make_static_string("hello");
Here we will be 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(const char (& str)[Size]) { return {str[Indexes] ..., '\0'}; } constexpr auto 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, 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> struct index_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> struct make_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> struct make_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(const char (& 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 the 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(const char (& 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.
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; }
Here, too, nothing complicated. Initialize the string with the array data:
template<size_t Size> std::string to_string(const static_string<Size>& str) { return std::string(str.data()); }
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 use recursion and the ternary 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 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> 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); }
This version of the comparator will allow us to compare not only strings entirely, but also individual substrings.
For concatenation we use the same variable pattern as in the chapter about creating a static string. Initialize the array first 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:
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) ...)); }
Consider the search for a character and a substring in a static string.
Searching for a symbol 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_npos
indicates that the search was not successful. We define it as follows:
constexpr size_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; }
To determine the occurrence of a character, try searching for it:
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; }
Counting the number of occurrences is implemented trivially:
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); }
Since it is assumed that the 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; }
To determine the occurrence of a substring, just try to search for it:
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; }
Using the previously described comparator, we can determine whether a static string begins with a given substring:
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; }
Similarly, to end a static line:
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; }
Here we look at the operations associated with spacing static strings.
As we noted earlier, to get 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> 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 ...> {};
We implement getting the substring with checking the beginning and end of the substring using 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>{}); }
The prefix is a substring whose beginning coincides with the beginning of the original static string:
template<size_t End, size_t Size> constexpr auto static_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> constexpr auto static_string_suffix(const static_string<Size>& str) { return static_string_substring<Begin, Size - 1>(str); }
To split a static string at a given index, it suffices to return the prefix and suffix:
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)); }
To reverse a static string, we write an index generator, which generates indexes in the reverse order:
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 ...> {};
Now we will implement a function that reverses the static string:
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>{}); }
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> 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; }
In this chapter, we consider the conversion of a static string to an integer, as well as the inverse transformation. For simplicity, we will assume that the numbers are represented by the types long long
and unsigned long long
, these are types of high capacity, that is, they are suitable for most cases.
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> struct char_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<unsigned long long Value, char ... Chars> struct make_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> struct make_unsigned_int_char_sequence<0, Chars ...> : char_sequence<Chars ...> {};
You should also take into account the case when the initial number is zero, in this case you need to return a null character, otherwise null will be converted to an empty sequence of characters, and then to an empty string:
template<> struct make_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, long long Value, char ... Chars> struct make_signed_int_char_sequence {};
We will process the number as well, as shown above, but taking into account the sign:
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 ...> {};
There is one subtle point here, pay attention to -(Value % 10)
. Here you cannot -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> 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 ...> {};
Separately, we take care of converting zero:
template<> struct make_signed_int_char_sequence<false, 0> : char_sequence<'0'> {};
Finally, we implement the conversion functions:
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>{}); }
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 corresponding numbers. We perform all actions recursively, return zero for an empty string:
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); }
To convert signed numbers, you need to consider that negative numbers start with a minus sign:
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); }
By this time the library is already possible to fully use, but some moments cause inconvenience. This chapter will look at how to make using the library more convenient.
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> 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
, , github
, .
_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, "");
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/428846/
All Articles