You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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支持存在局限性:

  1. 不能返回变长数组(VLA):C++标准不允许函数返回变长数组,而char result[final_length + 1]属于VLA,这是编译失败的核心原因之一。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:45:54