使用逗号运算符的参数包折叠表达式:展开时如何添加额外参数?
编译时字符串拼接并自动添加分隔符的简洁实现
我已经实现了一个编译时字符串类CTString,通过字符串字面量参数包完成拼接,核心基于逗号折叠表达式:
template<unsigned N> struct CTString { char m_chars[N + 1U]; template<unsigned... Ns> constexpr CTString(const char (&...s)[Ns]) { auto* p{ m_chars }; ((p = CopyN_(s, Ns - 1U, p)), ...); *p = '\0'; } constexpr char* CopyN_(const char* pFrom, unsigned size, char* pTo) { for (auto i{ 0U }; i < size; ++i) *(pTo++) = *(pFrom++); return pTo; } }; template<unsigned... Ns> constexpr auto concat(const char(&...s)[Ns]) { return CTString<(0U + ... + (Ns - 1U))>{s...}; } constexpr auto cHelloWorld{ concat("Hello", "World") }; static_assert(cHelloWorld.m_chars[9] == 'd'); static_assert(cHelloWorld.m_chars[10] == '\0');
现在需要给每个输入的字符串字面量后插入分隔符(比如"|"),但直接尝试(s, "|")...会因为逗号运算符丢弃左操作数而编译失败:
template<unsigned... Ns> constexpr auto concatWithSeparator(const char(&...s)[Ns]) { return CTString<(0U + ... + Ns)>{(s, "|")...}; } // 编译错误: constexpr auto cHelloCommaSeparated{ concatWithSeparator("Hello", "World") };
我可以用辅助类解决,但想知道有没有更简洁的惯用写法。
最简洁的实现:基于折叠表达式的直接处理
不需要修改原有CTString类,仅在concatWithSeparator函数里调整逻辑:通过折叠表达式一次性展开所有字符串和分隔符,最后回退指针去掉末尾多余的分隔符。这种写法紧凑且符合C++20参数包惯用风格:
template<unsigned... Ns> constexpr auto concatWithSeparator(const char(&...s)[Ns]) { constexpr char sep[] = "|"; constexpr unsigned sep_len = sizeof(sep) - 1U; // 总长度 = 所有字符串长度之和 + 分隔符数量(参数个数-1) constexpr unsigned total_len = (0U + ... + (Ns - 1U)) + (sizeof...(Ns) > 0U ? (sizeof...(Ns) - 1U) * sep_len : 0U); CTString<total_len> result; auto* p = result.m_chars; if constexpr (sizeof...(Ns) > 0U) { // 展开每个字符串+分隔符 ((p = result.CopyN_(s, Ns - 1U, p), p = result.CopyN_(sep, sep_len, p)), ...); // 回退指针,去掉最后一个多余的分隔符 p -= sep_len; } *p = '\0'; return result; } // 验证 constexpr auto cHelloPipeSeparated{ concatWithSeparator("Hello", "World") }; static_assert(cHelloPipeSeparated.m_chars[5] == '|'); static_assert(cHelloPipeSeparated.m_chars[10] == 'd'); static_assert(cHelloPipeSeparated.m_chars[11] == '\0');
另一种写法:带条件的折叠展开
如果不想依赖指针回退,也可以在折叠时判断是否为最后一个元素,避免添加多余分隔符。这种写法可读性更好,适合需要自定义分隔符逻辑的场景:
template<unsigned... Ns> constexpr auto concatWithSeparator(const char(&...s)[Ns]) { constexpr char sep[] = "|"; constexpr unsigned sep_len = sizeof(sep) - 1U; constexpr unsigned total_len = (0U + ... + (Ns - 1U)) + (sizeof...(Ns) > 0U ? (sizeof...(Ns) - 1U) * sep_len : 0U); CTString<total_len> result; auto* p = result.m_chars; bool is_first = true; (( [&](){ if (!is_first) { p = result.CopyN_(sep, sep_len, p); } is_first = false; p = result.CopyN_(s, Ns - 1U, p); }() ), ...); *p = '\0'; return result; }
这两种写法都不需要引入辅助类,完全基于C20的constexpr和参数包折叠特性,符合惯用的现代C风格。
内容的提问来源于stack exchange,提问作者eigenleif
相关产品推荐
相关产品推荐

