如何实现支持缩进感知的C++原始字符串字面量?
编译时实现缩进感知的原始字符串字面量(C17/C20)
针对你需要的编译时处理缩进感知原始字符串的需求,以下是两种适配C17和C20的方案:
C++17 constexpr编译时处理方案
利用C++17的constexpr特性,编写编译时函数计算最小缩进量并去除多余空格,完全在编译期完成处理,无运行时开销。
#include <string_view> #include <algorithm> #include <array> template <size_t N> constexpr auto dedent(const char (&str)[N]) { // 跳过开头的空行 size_t start = 0; while (start < N && (str[start] == '\n' || str[start] == '\r')) { ++start; } // 计算所有非空行的最小缩进量 size_t min_indent = static_cast<size_t>(-1); size_t pos = start; while (pos < N) { if (str[pos] == '\n') { ++pos; size_t indent = 0; while (pos < N && str[pos] == ' ') { ++indent; ++pos; } // 仅统计非空行的缩进 if (pos < N && str[pos] != '\n' && str[pos] != '\r') { min_indent = std::min(min_indent, indent); } } else { ++pos; } } // 处理无缩进的情况(如单行字符串) if (min_indent == static_cast<size_t>(-1)) { min_indent = 0; } // 构建去缩进后的结果字符串 std::array<char, N> result{}; size_t out_pos = 0; pos = start; bool in_indent = false; size_t current_indent = 0; while (pos < N) { if (str[pos] == '\n') { result[out_pos++] = str[pos++]; in_indent = true; current_indent = 0; } else if (in_indent && str[pos] == ' ') { if (current_indent < min_indent) { // 跳过最小缩进范围内的空格 ++current_indent; ++pos; } else { // 保留超出最小缩进的空格 result[out_pos++] = str[pos++]; in_indent = false; } } else { in_indent = false; result[out_pos++] = str[pos++]; } } // 去除末尾多余的换行和空格 while (out_pos > 0 && (result[out_pos-1] == '\n' || result[out_pos-1] == ' ')) { --out_pos; } result[out_pos] = '\0'; return std::string_view(result.data(), out_pos); } // 使用示例 #include <iostream> int main() { constexpr auto str = dedent(R"( Hello World )"); std::cout << "ref\n" << str; return 0; }
C++20 consteval增强方案
C++20的consteval关键字可以强制函数完全在编译期执行,避免任何运行时 fallback 的可能,同时结合std::span简化代码逻辑:
#include <string_view> #include <span> #include <algorithm> #include <array> consteval std::string_view dedent(std::string_view str) { // 跳过开头的空行 auto start = str.find_first_not_of("\n\r"); if (start == std::string_view::npos) { return ""; } str = str.substr(start); // 计算最小缩进量 size_t min_indent = static_cast<size_t>(-1); size_t pos = 0; while (pos < str.size()) { if (str[pos] == '\n') { ++pos; size_t indent = 0; while (pos < str.size() && str[pos] == ' ') { ++indent; ++pos; } if (pos < str.size() && str[pos] != '\n' && str[pos] != '\r') { min_indent = std::min(min_indent, indent); } } else { ++pos; } } if (min_indent == static_cast<size_t>(-1)) { min_indent = 0; } // 构建结果缓冲区(可根据实际需求调整大小) std::array<char, 1024> buf{}; size_t out_pos = 0; pos = 0; bool in_indent = false; size_t current_indent = 0; while (pos < str.size() && out_pos < buf.size() - 1) { if (str[pos] == '\n') { buf[out_pos++] = str[pos++]; in_indent = true; current_indent = 0; } else if (in_indent && str[pos] == ' ') { if (current_indent < min_indent) { ++current_indent; ++pos; } else { buf[out_pos++] = str[pos++]; in_indent = false; } } else { in_indent = false; buf[out_pos++] = str[pos++]; } } // 去除末尾空白字符 while (out_pos > 0 && (buf[out_pos-1] == '\n' || buf[out_pos-1] == ' ')) { --out_pos; } buf[out_pos] = '\0'; return std::string_view(buf.data(), out_pos); } // 使用示例 #include <iostream> int main() { constexpr auto str = dedent(R"( Hello World )"); std::cout << "ref\n" << str; return 0; }
额外说明
- 上述方案默认仅处理空格缩进,若需要支持制表符,可以在代码中添加制表符转空格的逻辑后再计算最小缩进。
- 缓冲区大小可根据实际使用场景调整,若需处理超长字符串,C++20支持
constexpr std::string,可以替换std::array实现动态大小的编译期字符串构造。
内容的提问来源于stack exchange,提问作者tejas
相关产品推荐
相关产品推荐

