带consteval关键字的Lambda在GCC/MSVC报错,LLVM可编译,谁合规?
编译器对consteval Lambda处理的差异问题
测试代码
#include <string> #include <string_view> int main() { static constexpr auto enlarge = [](std::string&& target, std::size_t const new_size) consteval { auto const old_size = target.size(); if(new_size < old_size) { throw "new size is smaller then old size"; } target.resize(new_size); target[old_size] = 0; return target.c_str(); }; using namespace std::literals; static_assert(enlarge("test"s, 32) == "test"sv); }
编译现象
- MSVC 19.33:报错提示即时函数调用不是常量表达式,且存在类型转换问题
- GCC 12.2:报错提示该表达式不是常量表达式,原因是引用了
operator new的结果 - LLVM 16(搭配libc的clang):可正常编译
- 移除Lambda的
consteval关键字后,三个编译器均可正常编译 - 补充:clang++ 16搭配libstdc++ 12.2时,无论是否添加
consteval,都会报错静态断言表达式不是整型常量表达式
问题分析与结论
LLVM(搭配libc++)的编译行为不符合C++标准,GCC和MSVC的报错是正确的,原因如下:
- consteval与constexpr的核心差异:
consteval函数要求所有调用都必须是合法的常量表达式,而constexpr函数仅要求存在至少一组实参能让它在常量表达式中执行,单个调用不一定必须满足常量表达式要求。 - 常量表达式中动态内存的规则:
C++标准允许在常量表达式中进行动态内存分配,但要求分配的内存必须在常量表达式求值结束前完全释放,否则整个表达式就不是合法的常量表达式。 - 本案例的违规点:
代码中enlarge函数返回target.c_str(),这是指向临时std::string对象内部缓冲区的指针。临时std::string对象的生命周期在static_assert的表达式求值结束后才会结束,意味着缓冲区的内存在常量表达式求值完成后仍未释放,违反了上述标准规则。因此这个consteval函数的调用本身就不是合法的常量表达式,GCC和MSVC的报错符合标准要求。 - 移除consteval后的情况:
移除consteval后,Lambda会自动成为constexpr Lambda(满足constexpr函数的要求),此时static_assert中的调用不需要强制是常量表达式(编译器会在运行时处理该逻辑),因此三个编译器都能正常编译。 - clang搭配libstdc的情况:
libstdc++对std::string的constexpr实现更为严格,不允许在常量表达式上下文中进行此类动态内存操作,因此无论是否添加consteval都会报错,这也是符合标准的行为。
内容的提问来源于stack exchange,提问作者Benjamin Buch
相关产品推荐
相关产品推荐

