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

C++ constexpr变量通过static_assert仍报非常量表达式错误

编译期字符串拼接常量表达式错误分析

问题现象

实现编译期字符串拼接功能时,注释代码末尾std::cout << joined<a,b,c> << std::endl;行可正常编译,取消注释则触发编译错误,复现代码如下:

#include <iostream>

constexpr const char a[] = "hello ";
constexpr const char b[] = "dear ";
constexpr const char c[] = "world!";

constexpr size_t size(const char* s)
{
    int i = 0;
    while(*s!=0) {
        ++i;
        ++s;
    }
    return i;
}

template <typename... Is, typename = std::enable_if_t<(... && std::is_same_v<const char*, Is>)>>
constexpr size_t calc_size(Is... values) {
    return (0 + ... + size(values));
}

constexpr bool strings_equal(char const * a, char const * b) {
    return *a == *b && (*a == '\0' || strings_equal(a + 1, b + 1));
}

template <const char*... S>
class cxpr_string
{
public:
    constexpr cxpr_string() : buf_{}, size_{0} {
        int i=0;
        ( [&]() {
            const size_t max = size(S);
            for (int i=0; i < max; ++i) {
                buf_[size_++] = S[i];
            }
        }(), ...);
        buf_[size_++] = 0;
    }

    constexpr const char* get() const {
        return buf_;
    }
private:
    char buf_[calc_size(S...)+1] = { 0 };
    size_t size_;
};

template <const char*... ptr>
constexpr auto joined = cxpr_string<ptr...>().get();

int main()
{
    static_assert(strings_equal(cxpr_string<a,b,c>().get(), "hello dear world!"));
    
    std::cout << joined<a,b,c> << std::endl; // 取消注释即报错
    std::cout << cxpr_string<a, b, c>().get() << std::endl;
}

报错信息如下:

<source>: In instantiation of 'constexpr const char* const joined<(& a), (& b), (& c)>':
<source>:56:18:   required from here
<source>:50:16: error: '(const char*)(&<anonymous>.cxpr_string<(& a), (& b), (& c)>::buf_)' is not a constant expression
   50 | constexpr auto joined = cxpr_string<ptr...>().get();
      |                ^~~~~~

不同编译器表现存在差异:GCC 12.1、Clang 14.0.0环境下编译失败,MSVC 19.31环境下可编译但运行失败。

根因说明

问题本质是违反了C++标准对常量表达式的生命周期约束:

  • static_assert中的调用可正常通过校验:该场景下临时创建的cxpr_string<a,b,c>对象全程在常量求值上下文内,strings_equal直接逐字符读取临时对象内部buf_的内容完成比较,不需要将buf_的地址持久存储到跨表达式的常量中,因此符合常量表达式规则。
  • joined变量初始化非法:joined是命名空间作用域的constexpr变量,初始化式返回的是临时cxpr_string对象内部buf_数组的首地址。C++标准明确禁止将临时对象的地址作为常量表达式的最终结果——临时对象在所属完整表达式结束后就会被销毁,将其地址存储到全局常量中必然产生悬空指针,因此编译器判定该表达式不属于合法常量表达式。
  • 直接输出临时对象get()结果可正常编译:std::cout << cxpr_string<a, b, c>().get() << std::endl;中临时对象的生命周期覆盖整个cout表达式的执行周期,编译器不需要将buf_地址持久存储到全局常量中,因此不会触发规则限制。
  • MSVC的异常表现属于标准符合性缺陷:它错误接受了非法的常量表达式初始化,但运行时临时对象已经销毁,访问悬空指针直接导致运行崩溃。

修复方案

不要将临时对象内部缓冲区的裸指针存储为全局常量,改为直接存储cxpr_string类型的constexpr实例:

template <const char*... ptr>
constexpr cxpr_string<ptr...> joined{};

使用时调用joined<a,b,c>.get()即可。此时joined本身是静态存储期的常量对象,内部buf_的地址固定合法,不存在悬空指针问题。

内容的提问来源于stack exchange,提问作者glades

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 12:21:19