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

使用逗号运算符的参数包折叠表达式:展开时如何添加额外参数?

编译时字符串拼接并自动添加分隔符的简洁实现

我已经实现了一个编译时字符串类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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 23:44:55