
如果使用C ++编程,您可能想知道为什么不能比较两个字符串文字或执行它们的串联:
auto str = "hello" + "world";
但是,正如他们所说,“这是不可能的,但如果您真的想要,那么就可以。” 我们将在刻板刻之下打破刻板印象,而在编译阶段就刻板印象。
为什么需要所有这些
在我正在从事的项目之一中,习惯将std :: string用作字符串常量。 该项目有几个模块,其中定义了全局字符串常量:
我想您已经猜到有一天发生了什么。 尽管实际情况下PLUGIN_PATH
的值为"/usr/local/lib/project/plugins/"
,但SAMPLE_PLUGIN_PATH
的值为"sample.so"
SAMPLE_PLUGIN_PATH
"/usr/local/lib/project/plugins/"
。 怎么会这样 一切都非常简单,没有定义全局对象的初始化顺序,在初始化SAMPLE_PLUGIN_PATH
变量PLUGIN_PATH
为空。
另外,这种方法具有许多缺点。 首先,不会捕获在创建全局对象时引发的异常。 其次,初始化发生在程序执行期间,这花费了宝贵的处理器时间。
那时,我有了在编译阶段使用字符串的想法,最终导致撰写本文。
在本文中,我们将考虑可在编译阶段进行操作的行。 我们将这些行称为静态。
所有实现的操作都包含在库中,用于处理静态字符串。 库源代码可在github上找到,链接在文章结尾。
使用该库至少需要C ++ 14。
静态字符串定义
我们将静态字符串定义为字符数组,为方便起见,我们假设字符串始终以空字符结尾:
template<size_t Size> using static_string = std::array<const char, Size>; constexpr static_string<6> hello = {'H', 'e', 'l', 'l', 'o', '\0'};
在这里,您可以采用其他方法,并将字符串定义为字符元组。 在我看来,此选项更耗时且不方便。 因此,这里将不考虑。
创建一个静态字符串
看看上面的hello行的定义,这太糟糕了。 首先,我们需要预先计算数组的长度。 其次,您必须记住在最后写空字符。 第三,所有这些逗号,方括号和引号。 肯定有一些事情要做。 我想写这样的东西:
constexpr auto hello = make_static_string("hello");
这里,变量模板的一种形式将对我们有所帮助,这使我们能够将模板参数扩展为从字符串文字中静态字符串的聚合初始化的索引:
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");
已经更好了,但是索引仍然必须手工编写。 在这里,我们还注意到,如果不指定所有索引,则可以获取字符串文字的子字符串,如果以相反的顺序编写它们,则其取反:
constexpr hello1 = make_static_string<1, 2, 3>("hello");
这种考虑对我们将来将非常有用。
现在,我们需要以某种方式生成一系列行索引。 为此,请应用继承技巧。 使用一组必需的索引作为模板参数定义一个空结构(您需要继承一些东西):
template<size_t ... Indexes> struct index_sequence {};
我们定义了一个生成器结构,该结构将一次生成一个索引,并将计数器存储在第一个参数中:
template<size_t Size, size_t ... Indexes> struct make_index_sequence : make_index_sequence<Size - 1, Size - 1, Indexes ...> {};
我们还将注意递归端点,当所有索引都生成时(计数器为零),我们将丢弃该计数器,并且生成器将变为所需的序列:
template<size_t ... Indexes> struct make_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {};
结果,创建静态字符串的功能将如下所示:
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'}; }
我们将为静态字符串编写一个类似的函数,这对我们进一步有用:
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'}; }
此外,对于每个采用字符串文字foo(const char (& str)[Size])
函数,我们将编写一个类似的采用静态字符串foo(const static_string<Size>& str)
。 但是为了简洁起见,我不会提及这一点。
由于我们知道字符串文字的长度,因此我们可以自动生成一个索引序列,我们将为上面的函数编写一个包装器:
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>{}); }
此功能使我们可以按照本章开头的要求做确切的事情。
如果没有参数,我们将返回一个空的静态字符串,该字符串仅包含一个空字符:
constexpr static_string<1> make_static_string() { return {'\0'}; }
我们还需要从一个字符元组创建一个字符串:
template<char ... Chars> constexpr static_string<sizeof ... (Chars) + 1> make_static_string(char_sequence<Chars ...>) { return {Chars ..., '\0'}; }
顺便说一下,本文稍后将描述的所有内容均基于本章中描述的技术。 因此,如果仍然无法理解,最好再次阅读本章。
将静态字符串输出到流
这里的一切都很简单。 由于我们的字符串以空字符结尾,因此将数组数据输出到流就足够了:
template<size_t Size> std::ostream& operator<<(std::ostream& os, const static_string<Size>& str) { os << str.data(); return os; }
将静态字符串转换为std :: string
这里也没有什么复杂的。 我们使用数组数据初始化字符串:
template<size_t Size> std::string to_string(const static_string<Size>& str) { return std::string(str.data()); }
静态字符串比较
我们将逐个字符地比较行,直到找到差异,或者直到到达至少一行的末尾为止。 由于尚未发明constexpr,因此我们使用递归和三元运算符:
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); }
将来,我们将需要扩展版本的比较器,我们将为它们的每一行引入一个单独的索引,并且还将限制比较字符的数量:
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); }
此版本的比较器将使我们不仅可以比较整个字符串,还可以比较单个子字符串。
静态字符串的串联
对于串联,我们使用与创建静态字符串这一章相同的变量模板。 首先使用第一行的字符(不包括空字符)初始化数组,然后使用第二行,最后将空字符添加到末尾:
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>{}); }
我们还实现了一个变量模板,用于连接任意数量的字符串或字符串文字:
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) ...)); }
静态字符串搜索操作
考虑在静态字符串中查找字符和子字符串的操作。
在静态字符串中搜索字符
字符搜索并不是特别困难,可以递归检查所有索引的字符,并在匹配的情况下返回第一个索引。 我们还将提供机会设置搜索的初始位置和匹配的序列号:
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; }
常量static_string_npos
表示搜索失败。 我们将其定义如下:
constexpr size_t static_string_npos = std::numeric_limits<size_t>::max();
同样,我们以相反的方向实现搜索:
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; }
确定字符在静态字符串中的出现
要确定某个字符的出现,只需尝试寻找它:
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; }
计算静态字符串中字符出现的次数
计算发生次数很简单:
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); }
在静态字符串中搜索子字符串
由于假定静态行将相对较小,因此在此我们将不实施Knut-Morris-Pratt算法 ,因此将实施最简单的二次算法:
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; }
同样,我们以相反的方向实现搜索:
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; }
确定子字符串是否在静态字符串中
要确定子字符串的出现,只需尝试寻找它:
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; }
确定静态字符串是否在给定子字符串上以/开头/结尾
应用前面描述的比较器,我们可以确定静态字符串是否以给定的子字符串开头:
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; }
结束一条静态行的方法类似:
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; }
使用静态字符串子字符串
在这里,我们看一下与静态字符串的子字符串关联的操作。
获取静态字符串的子字符串,前缀和后缀
如前所述,要获取子字符串,您需要使用给定的起始索引和结束索引生成一系列索引:
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 ...> {};
我们使用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>{}); }
前缀是一个子字符串,其开头与原始静态行的开头重合:
template<size_t End, size_t Size> constexpr auto static_string_prefix(const static_string<Size>& str) { return static_string_substring<0, End>(str); }
对于后缀类似,只有结尾匹配:
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); }
在给定的索引处将静态字符串分为两部分
要在给定索引处拆分静态字符串,只需返回前缀和后缀即可:
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)); }
静态字符串反转
为了反转静态字符串,我们编写了一个索引生成器,该生成器以相反的顺序生成索引:
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 ...> {};
现在,我们实现了一个可以反转静态字符串的函数:
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>{}); }
静态字符串哈希计算
我们将使用以下公式计算哈希值:
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; }
将数字转换为静态字符串,反之亦然
在本章中,我们着眼于将静态字符串转换为整数以及逆向。 为简单起见,我们假定数字由long long
和unsigned long long
类型表示,它们是大容量的类型,也就是说,它们适合大多数情况。
将数字转换为静态字符串
要将数字转换为静态字符串,我们需要获取数字的所有数字,将其转换为相应的字符,然后将这些字符制成字符串。
要获取数字的所有数字,我们将使用类似于索引序列生成器的生成器。 定义字符序列:
template<char ... Chars> struct char_sequence {};
我们实现了一个数字字符生成器,将当前数字存储在第一个参数中,并将数字存储在以下参数中,将下一个数字添加到序列的开头,然后将数字除以十:
template<unsigned long long Value, char ... Chars> struct make_unsigned_int_char_sequence : make_unsigned_int_char_sequence<Value / 10, '0' + Value % 10, Chars ...> {};
如果当前数字为0,则我们将其丢弃,返回一个数字序列,没有其他要转换的内容:
template<char ... Chars> struct make_unsigned_int_char_sequence<0, Chars ...> : char_sequence<Chars ...> {};
您还应该考虑初始数字为零的情况,在这种情况下,您需要返回一个空字符,否则零将被转换为空字符序列,然后转换为空字符串:
template<> struct make_unsigned_int_char_sequence<0> : char_sequence<'0'> {};
实现的生成器非常适合于正数,但不适用于负数。 我们通过在开头添加另一个模板参数(即转换后的数字的符号)来定义新的生成器:
template<bool Negative, long long Value, char ... Chars> struct make_signed_int_char_sequence {};
我们将如上所述处理号码,但要考虑符号:
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 ...> {};
这里有一个微妙的地方,注意-(Value % 10)
。 在这里,您不能-Value % 10
,因为负数的范围比正数的范围宽一个数,并且最小模数落在有效值集中。
我们将处理后的数字丢弃,如果为负数,请添加减号符号:
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 ...> {};
另外,我们负责转换零:
template<> struct make_signed_int_char_sequence<false, 0> : char_sequence<'0'> {};
最后,我们实现转换功能:
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>{}); }
将静态字符串转换为数字
要将静态字符串转换为数字,您需要将字符转换为数字,然后将它们相加(先前已将十乘以相应的幂)。 我们递归执行所有操作,对于一个空字符串,我们返回零:
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); }
要转换带符号的数字,请注意负数以减号开头:
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); }
图书馆可用性注意事项
至此,已经可以完全使用该库了,但是有些地方不方便。 在本章中,我们将研究如何使使用库更加方便。
静态字符串对象
将字符串和已实现的方法打包到一个对象中。 这将允许使用较短的方法名称,并实现比较运算符:
template<size_t Size> struct static_string { constexpr size_t length() const { return Size - 1; } constexpr size_t size() const { return Size; } constexpr size_t begin() const { return 0; } constexpr size_t end() const { return Size - 1; } constexpr size_t rbegin() const { return Size - 2; } constexpr size_t rend() const { return std::numeric_limits<size_t>::max(); } constexpr bool empty() const { return Size < 2; } constexpr auto reverse() const { return static_string_reverse(*this); } template<size_t Begin, size_t End> constexpr auto substring() const { return static_string_substring<Begin, End>(*this); } template<size_t End> constexpr auto prefix() const { return static_string_prefix<End>(*this); } template<size_t Begin> constexpr auto suffix() const { return static_string_suffix<Begin>(*this); } constexpr size_t find(char ch, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t find(const static_string<SubSize>& substr, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t find(const char (& substr)[SubSize], size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } constexpr size_t rfind(char ch, size_t from = Size - 2, size_t nth = 0) const { return static_string_rfind(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t rfind(const static_string<SubSize>& substr, size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t rfind(const char (& substr)[SubSize], size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } constexpr bool contains(char ch) const { return static_string_contains(*this, ch); } template<size_t SubSize> constexpr bool contains(const static_string<SubSize>& substr) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool contains(const char (& substr)[SubSize]) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool starts_with(const static_string<SubSize>& prefix) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool starts_with(const char (& prefix)[SubSize]) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool ends_with(const static_string<SubSize>& suffix) const { return static_string_ends_with(*this, suffix); } template<size_t SubSize> constexpr bool ends_with(const char (& suffix)[SubSize]) const { return static_string_ends_with(*this, suffix); } constexpr size_t count(char ch) const { return static_string_count(*this, ch); } template<size_t Index> constexpr auto split() const { return static_string_split<Index>(*this); } constexpr unsigned long long hash() const { return static_string_hash(*this); } constexpr char operator[](size_t index) const { return data[index]; } std::string str() const { return to_string(*this); } std::array<const char, Size> data; };
. :
template<size_t Size1, size_t Size2> constexpr bool operator<(const static_string<Size1>& str1, const static_string<Size2>& str2) { return static_string_compare(str1, str2) < 0; }
> <= >= == !=, . - .
:
#define ITOSS(x) int_to_static_string<(x)>() #define UTOSS(x) uint_to_static_string<(x)>() #define SSTOI(x) static_string_to_int((x)) #define SSTOU(x) static_string_to_uint((x))
.
:
constexpr auto hello = make_static_string("Hello"); constexpr auto world = make_static_string("World"); constexpr auto greeting = hello + ", " + world + "!";
, :
constexpr int apples = 5; constexpr int oranges = 7; constexpr auto message = static_string_concat("I have ", ITOSS(apples), " apples and ", ITOSS(oranges), ", so I have ", ITOSS(apples + oranges), " fruits");
constexpr unsigned long long width = 123456789ULL; constexpr unsigned long long height = 987654321ULL; constexpr auto message = static_string_concat("A rectangle with width ", UTOSS(width), " and height ", UTOSS(height), " has area ", UTOSS(width * height));
constexpr long long revenue = 1'000'000LL; constexpr long long costs = 1'200'000LL; constexpr long long profit = revenue - costs; constexpr auto message = static_string_concat("The first quarter has ended with net ", (profit >= 0 ? "profit" : "loss "), " of $", ITOSS(profit < 0 ? -profit : profit));
URL:
constexpr auto url = make_static_string("http://www.server.com:8080"); constexpr auto p = url.find("://"); constexpr auto protocol = url.prefix<p>();
:
constexpr auto str = make_static_string("Hello"); for (size_t i = str.begin(); i != str.end(); ++i)
参考文献
, , github
, .
Update
_ss :
template<typename Char, Char ... Chars> constexpr basic_static_string<Char, sizeof ... (Chars) + 1> operator"" _ss() { return {Chars ..., static_cast<Char>('\0')}; };
make_static_string() , :
constexpr auto hello_world = "Hello"_ss + " World"; if ("Hello" < "World"_ss) { ... } constexpr auto hash = "VeryLongString"_ss.hash();
Char char:
template<typename Char, size_t Size> struct basic_static_string {
char whar_t, , concat, :
template<size_t Size> using static_string_t = basic_static_string<char, Size>; template<size_t Size> using static_wstring_t = basic_static_string<wchar_t, Size>; using static_string = basic_static_string<char, 0>; using static_wstring = basic_static_string<wchar_t, 0>;
"" :
constexpr auto wide_string = L"WideString"_ss; constexpr int apples = 5; constexpr int oranges = 7; constexpr int fruits = apples + oranges; constexpr auto str3 = static_wstring::concat(L"I have ", ITOSW(apples), L" apples and ", ITOSW(oranges), L" oranges, so I have ", ITOSW(fruits), L" fruits"); static_assert(str3 == L"I have 5 apples and 7 oranges, so I have 12 fruits", ""); std::wcout << str3 << std::endl;
size(), size() length() , sizeof():
constexpr auto ss1 = "Hello"_ss; static_assert(ss1.length() == 5, ""); static_assert(ss1.size() == 5, ""); static_assert(sizeof(ss1) == 6, "");
github
.
Update 2
AndreySu , :
#include <iostream> using namespace std; template<typename Char, Char ... Chars> struct static_string{}; template<typename Char, Char ... Chars1, Char ... Chars2> constexpr static_string<Char, Chars1 ..., Chars2 ... > operator+( const static_string<Char, Chars1 ... >& str1, const static_string<Char, Chars2 ... >& str2) { return static_string<Char, Chars1 ..., Chars2 ...>{}; } template<typename Char, Char ch, Char ... Chars> std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& bos, const static_string<Char, ch, Chars ...>& str) { bos << ch << static_string<Char, Chars ... >{}; return bos; } template<typename Char> std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& bos, const static_string<Char>& str) { return bos; } template<typename Char, Char ... Chars> constexpr static_string<Char, Chars ... > operator"" _ss() { return static_string<Char, Chars ... >{}; }; int main() { constexpr auto str1 = "abc"_ss; constexpr auto str2 = "def"_ss; constexpr auto str = str1 + str2 + str1; std::cout << str << std::endl; return 0; }
, , - .