C++17中实现依赖输入内容的constexpr数组[N]转数组[M]方法问询
C++17下实现无内存浪费的constexpr字符串转义函数
我需要把以下string版本的转义函数:
string escape(const string& s) { string r = ""; for(char c:s) { if(c=='"') { r+='\\'; r+=c; } else r+=c; } return r; }
转换成类似这样的constexpr版本:
constexpr const char* escape(const char* s) { ??? }
这个需求是为了演示**通用constexpr数组[N]转数组[M]**的方案,其中M的大小完全取决于输入数组[N]的内容。我的平台对RAM要求很高,希望尽可能把数据放到只读段,同时保持代码清晰,而且不想为这个问题切换到C20,所以想知道C17下是否可行。
我试过基于输出上限的方案:输入有N个字符时,输出最大长度是2*N(最坏情况是全引号的字符串),代码大概是这样:
template<int N> struct static_str { char data[N]; }; template<int N> constexpr static_str<2*N> escape(static_str<N> s) { static_str<2*N> r; int k=0; for(int i=0;i<N;i++) { if(s.data[i]=='"') { r.data[k++]='\\'; } r.data[k++] = s.data[i]; } return r; }
但这个方法会浪费内存,我有几个思路但都受限于C++17的规则:
- 可以预计算输出长度
expected_len(s) = strlen(s) + <引号数量>,但没法创建static_str<expected_len(s)>类型的变量,编译器提示“expected_len(s)不是常量”; - 尝试把上述escape函数得到的2*N大小的输出截断到
expected_len(s),但不知道怎么操作不违反模板机制; - 如果输出大小难以预先确定的话,该怎么处理?
解决方案:利用constexpr计算长度+模板辅助结构
在C++17中,虽然不能直接用运行时值作为模板参数,但可以通过编译期常量计算结合模板非类型参数实现无内存浪费的constexpr转义,同时确保数据存入只读段。
步骤1:实现constexpr长度计算函数
先写一个编译期就能算出转义后长度的函数:
constexpr size_t escaped_len(const char* s) { size_t len = 0; while (*s) { len += (*s == '"') ? 2 : 1; ++s; } return len + 1; // 加上字符串终止符'\0' }
步骤2:用模板结构体生成精确大小的转义字符串
通过模板结构体,将输入字符串作为编译期非类型参数,同时自动推导转义后的长度,在结构体内部完成转义逻辑:
template<const char* S, size_t Len = escaped_len(S)> struct escaped_string { static constexpr char value[Len]; }; // 实现结构体的静态成员(编译期初始化) template<const char* S, size_t Len> constexpr char escaped_string<S, Len>::value[Len] = []() { char result[Len] = {}; size_t idx = 0; for (size_t i = 0; S[i]; ++i) { if (S[i] == '"') { result[idx++] = '\\'; } result[idx++] = S[i]; } result[idx] = '\0'; // 确保字符串终止 return result; }();
步骤3:使用方式
这样就能在编译期生成刚好大小的转义字符串,且存储在只读段:
// 定义编译期输入字符串 constexpr const char input[] = "Hello \"World\"!"; // 获取编译期转义后的结果 constexpr const char* output = escaped_string<input>::value; // 编译期验证结果正确性 static_assert(output[6] == '\\'); static_assert(output[7] == '"');
针对问题的解答
- 动态长度作为模板参数的问题:C++17不允许直接用函数返回值作为模板参数,但把字符串作为模板非类型参数后,
escaped_len(S)是编译期可确定的常量表达式,可以作为默认模板参数使用,完美绕过限制。 - 截断输出的问题:上述方案直接生成精确长度的数组,从根源避免了内存浪费,不需要额外截断操作。
- 输出大小难以预测的情况:只要输入是编译期已知的constexpr字符串,就能通过constexpr函数计算出精确长度;如果输入是运行时字符串,C++17无法在编译期处理,只能使用上限方案或动态分配,但你的需求是存入只读段,因此输入必须是编译期常量。
适配static_str的版本
如果要兼容你之前定义的static_str结构,可以修改为以下实现:
template<int N> struct static_str { char data[N]; }; // 计算static_str的转义后长度 template<int N> constexpr size_t escaped_len(const static_str<N>& s) { size_t len = 0; for (int i = 0; s.data[i]; ++i) { len += (s.data[i] == '"') ? 2 : 1; } return len + 1; } // 辅助结构体 template<typename StaticStr, size_t Len = escaped_len(StaticStr{})> struct escaped_static_str; template<int N, size_t Len> struct escaped_static_str<static_str<N>, Len> { static constexpr static_str<Len> value = []() { static_str<Len> result{}; size_t idx = 0; for (int i = 0; static_str<N>{}.data[i]; ++i) { if (static_str<N>{}.data[i] == '"') { result.data[idx++] = '\\'; } result.data[idx++] = static_str<N>{}.data[i]; } result.data[idx] = '\0'; return result; }(); }; // 使用示例 constexpr static_str<14> input = {"Hello \"World\"!"}; constexpr auto output = escaped_static_str<decltype(input)>::value;
内容的提问来源于stack exchange,提问作者Stefan Neacsu
相关产品推荐
相关产品推荐

