GCC、LLVM与MSVC对constexpr std::string运行时传递的分歧谁正确?
编译期std::string跨环境传递的编译器行为分歧
我尝试在编译期创建一个std::string并将其内容传递到运行时环境,却遇到了不同编译器行为不一致的问题,代码如下:
#include <algorithm> #include <string> template <std::size_t N> struct ct_to_rt_string { consteval ct_to_rt_string(std::string const str) { std::copy_n(str.c_str(), N + 1, buffer); } char buffer[N + 1]; }; constexpr std::string make_ct_std_string() { return "compile time text"; } #include <iostream> int main() { std::cout << ct_to_rt_string<make_ct_std_string().size()>(make_ct_std_string()).buffer; }
不同编译器的表现
GCC 12.2(libstdc++)
可正常编译并输出预期结果:
compile time text
LLVM 16(libc++)
报错,提示目标表达式不是常量表达式,根源是动态内存未释放:
<source>:20:34: error: non-type template argument is not a constant expression std::cout << ct_to_rt_string<make_ct_std_string().size()>(make_ct_std_string()).buffer; ^~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/clang-16.0.0/bin/../include/c++/v1/__memory/allocator.h:113:38: note: allocation performed here was not deallocated return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp))); ^ 1 error generated. ASM generation compiler returned: 1 <source>:20:34: error: non-type template argument is not a constant expression std::cout << ct_to_rt_string<make_ct_std_string().size()>(make_ct_std_string()).buffer; ^~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/clang-16.0.0/bin/../include/c++/v1/__memory/allocator.h:113:38: note: allocation performed here was not deallocated return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp))); ^ 1 error generated.
MSVC 19.35
报错,提示立即函数调用不是常量表达式:
<source>(20): error C7595: 'ct_to_rt_string<17>::ct_to_rt_string': call to immediate function is not a constant expression
哪款编译器的行为符合标准?
LLVM和MSVC的行为是正确的,GCC的实现依赖了非标准细节,不符合C++标准要求。
原因如下:
- C++标准明确规定:在常量表达式的求值过程中,如果执行了动态内存分配,那么所有分配的内存必须在同一个常量表达式的求值周期内被释放。任何未被释放的动态内存都会导致整个表达式失去常量表达式的合法性。
- 你的代码中,
make_ct_std_string()返回的std::string对象在作为模板参数(make_ct_std_string().size())求值时,其内部的动态内存(若有分配)要等到整个模板参数表达式求值完成后才会被析构函数释放,这违反了标准中关于常量表达式内存管理的规则。 - GCC能通过编译,是因为libstdc++的
std::string对短字符串使用了短字符串优化(SSO),此时没有触发动态内存分配,因此GCC认为该表达式是合法的常量表达式。但这是依赖实现细节的特殊情况,一旦字符串长度超过SSO的阈值,GCC同样会报错。
修正方案
要实现编译期到运行时的字符串传递,建议使用无动态内存分配的编译期字符串类型,比如std::string_view或者constexpr字符数组:
方案1:使用std::string_view
#include <algorithm> #include <string_view> template <std::size_t N> struct ct_to_rt_string { consteval ct_to_rt_string(std::string_view str) { std::copy_n(str.data(), N + 1, buffer); } char buffer[N + 1]; }; constexpr std::string_view make_ct_string_view() { return "compile time text"; } #include <iostream> int main() { constexpr auto sv = make_ct_string_view(); std::cout << ct_to_rt_string<sv.size()>(sv).buffer; }
方案2:使用constexpr字符数组
#include <algorithm> template <std::size_t N> struct ct_to_rt_string { consteval ct_to_rt_string(const char (&str)[N]) { std::copy_n(str, N, buffer); } char buffer[N]; }; constexpr const char ct_text[] = "compile time text"; #include <iostream> int main() { std::cout << ct_to_rt_string<sizeof(ct_text)>(ct_text).buffer; }
内容的提问来源于stack exchange,提问作者Benjamin Buch
相关产品推荐
相关产品推荐

