如何通过constexpr函数实现const char[]字符串的精简与归一
实现constexpr字符串精简函数strip_string的问题
需求
- 修剪字符串:移除首尾连续空白字符
- 归一化字符串中间的连续空白字符为单个空格
- 返回最终长度的
const char*常量,确保二进制文件仅包含精简后的字符串,而非原字符串 - 结果字符串长度为精简后的实际长度,而非原字符串长度
目标:源码中保留可读性强的格式化字符串,二进制文件仅存储最短的精简后字符串,使用Visual Studio C++ 2017。
示例
源码中保留的格式化字符串:
R"SQL( CREATE TABLE IF NOT EXISTS "MyTable" ( "ID" INTEGER NOT NULL, "Field1" TEXT NOT NULL, "Field2" TEXT, PRIMARY KEY("ID") ) )SQL"
二进制中仅存储的精简结果:
CREATE TABLE IF NOT EXISTS "MyTable" ( "ID" INTEGER NOT NULL, "Field1" TEXT NOT NULL, "Field2" TEXT, PRIMARY KEY("ID"))
尝试代码(编译失败)
#include <cstddef> #include <utility> #include <iostream> constexpr bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; } template<std::size_t N> constexpr std::size_t calculate_final_length(const char (&input)[N]) { std::size_t start = 0; std::size_t end = N - 1; while (start < end && is_whitespace(input[start])) { ++start; } while (end > start && is_whitespace(input[end - 1])) { --end; } std::size_t trimmed_length = end - start; std::size_t final_length = 0; bool in_whitespace = false; for (std::size_t i = 0; i < trimmed_length; ++i) { char c = input[start + i]; if (is_whitespace(c)) { if (!in_whitespace) { ++final_length; in_whitespace = true; } } else { ++final_length; in_whitespace = false; } } return final_length; } template<std::size_t N> constexpr auto strip_string(const char (&input)[N]) { constexpr std::size_t final_length = calculate_final_length(input); // 无法转为常量 char result[final_length + 1] = {}; std::size_t out_index = 0; bool in_whitespace = false; // 计算修剪后的首尾索引 std::size_t start = 0; std::size_t end = N - 1; while (start < end && is_whitespace(input[start])) { ++start; } while (end > start && is_whitespace(input[end - 1])) { --end; } // 计算修剪后的长度 std::size_t trimmed_length = end - start; // 填充结果数组 for (std::size_t i = 0; i < trimmed_length; ++i) { char c = input[start + i]; if (is_whitespace(c)) { if (!in_whitespace) { result[out_index++] = ' '; in_whitespace = true; } } else { result[out_index++] = c; in_whitespace = false; } } result[out_index] = '\0'; return result; } constexpr const char raw_string[] = ; constexpr auto test = strip_string( R"( This is a RAW string test )"); int main() { std::cout << test; std::cout << '\n'; return 0; }
问题分析与解决方案
VS2017的C++17支持存在局限性:
- 不能返回变长数组(VLA):C++标准不允许函数返回变长数组,而
char result[final_length + 1]属于VLA,这是编译失败的核心原因之一。 - constexpr函数的限制:VS2017对constexpr的支持不完全符合C++17标准,比如在constexpr上下文中处理数组的能力较弱。
修正方案
使用std::array<char, ...>作为返回类型,利用模板元编程在编译期计算最终长度并生成精简后的字符串:
#include <cstddef> #include <array> #include <iostream> constexpr bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; } // 编译期计算精简后的字符串长度 template<std::size_t N> constexpr std::size_t calculate_final_length(const char (&input)[N]) { std::size_t start = 0; while (start < N - 1 && is_whitespace(input[start])) { ++start; } std::size_t end = N - 1; while (end > start && is_whitespace(input[end - 1])) { --end; } std::size_t final_len = 0; bool in_space = false; for (std::size_t i = start; i < end; ++i) { if (is_whitespace(input[i])) { if (!in_space) { ++final_len; in_space = true; } } else { ++final_len; in_space = false; } } return final_len; } // 编译期填充精简后的字符串到std::array template<std::size_t N, std::size_t FinalLen> constexpr std::array<char, FinalLen + 1> build_stripped_string(const char (&input)[N]) { std::array<char, FinalLen + 1> result{}; std::size_t out_idx = 0; bool in_space = false; std::size_t start = 0; while (start < N - 1 && is_whitespace(input[start])) { ++start; } std::size_t end = N - 1; while (end > start && is_whitespace(input[end - 1])) { --end; } for (std::size_t i = start; i < end; ++i) { if (is_whitespace(input[i])) { if (!in_space) { result[out_idx++] = ' '; in_space = true; } } else { result[out_idx++] = input[i]; in_space = false; } } result[out_idx] = '\0'; return result; } // 对外接口:自动推导最终长度 template<std::size_t N> constexpr auto strip_string(const char (&input)[N]) { constexpr std::size_t final_len = calculate_final_length(input); return build_stripped_string<N, final_len>(input); } // 测试用例 constexpr auto sql_query = strip_string(R"SQL( CREATE TABLE IF NOT EXISTS "MyTable" ( "ID" INTEGER NOT NULL, "Field1" TEXT NOT NULL, "Field2" TEXT, PRIMARY KEY("ID") ) )SQL"); constexpr auto test_str = strip_string(R"( This is a RAW string test )"); int main() { std::cout << sql_query.data() << '\n'; std::cout << test_str.data() << '\n'; return 0; }
关键改进点
- 使用
std::array替代变长数组,符合C++标准,且能在编译期生成固定大小的常量数组 - 将长度计算与字符串构建拆分,简化constexpr上下文的逻辑,适配VS2017的constexpr支持
- 返回的
std::array在编译期完全初始化,二进制文件中仅存储精简后的字符串,原字符串不会被保留(只要原字符串是constexpr参数,编译器会优化掉)
内容的提问来源于stack exchange,提问作者hlide
相关产品推荐
相关产品推荐

