为什么局部 constexpr std::string 会编译失败?
结论
这段代码能否通过编译,取决于 std::string 对象自身的表示。诊断显示,当前实现将 _M_dataplus 指针设为指向对象内部的 _M_local_buf。对于自动存储期的局部对象,这个自指针可能不符合常量表达式的规则,因此局部声明会失败。
全局的 constexpr std::string 具有静态存储期。Foo 只包含整数和字符数组。这两点可以解释示例中三种声明为何表现不同。
诊断对应的内部表示
错误信息中包含这一部分:
_M_dataplus{((char*)(& str15...::_M_local_buf))}
这表明该 std::string 实例使用了内部缓冲区,内部指针指向自己的 _M_local_buf。长度为 15 的字符串在这次实现中使用了内部缓冲区。具体容量和对象布局属于标准库实现细节。
常量表达式对指针值有额外限制。指向静态存储期对象的指针可以在常量表达式中使用;指向自动存储期对象的指针,则只有满足常量求值相关的生命周期条件时才能使用。这里,局部字符串的内部指针指向一个自动存储期对象的子对象,因此编译器拒绝了该初始化。
为什么 Foo 可以
Foo 只有 int 和 char[8] 成员。构造函数在常量求值期间为成员赋值,无需在对象中保存指向自身的地址,因此没有遇到同类的指针限制。
字符串字面量 "123456789012345" 本身也没有问题。全局声明已经表明,当前编译环境可以用它初始化 constexpr std::string。差异来自构造完成后的对象表示和对象的存储期。
可用的处理方式
如果需要编译期字符串数据,可以使用字符数组;如果只需要非拥有型视图,可以使用 std::string_view:
constexpr char text[] = "123456789012345";
constexpr std::string_view view = text;
如果需要局部拥有字符串,但不要求编译期常量,可以改用普通的 const std::string:
void func()
{
const std::string text{"123456789012345"};
}
如果确实需要 constexpr std::string,可以尝试使用具有静态存储期的对象,例如命名空间作用域变量。能否编译仍取决于 C++ 标准版本、编译器和标准库实现。std::string 从 C++20 开始支持 constexpr,较早的工具链可能不支持,具体边界也可能因实现而异。
注意事项
不能由此推断所有局部 constexpr std::string 都会失败,也不能把 std::string 的内部缓冲区布局视为标准保证。应结合目标编译器、标准库和语言标准版本判断。你提供的诊断表明,当前实现遇到的是指针指向局部对象内部缓冲区的问题。
备注:内容仅供参考。
