consteval对函数表达式分类/求值的影响及代码报错解析
相关代码
#include <string_view> #include <iostream> #include <cstring> consteval const char* compile_time_trim(const std::string_view& s) noexcept{ return s.data() + s.find("/app/") + strlen("/app/"); } constexpr const char* best_effort_trim(const std::string_view& s) noexcept{ if consteval { return compile_time_trim(s); } else { return s.data(); } } int main() { std::cout << best_effort_trim(__FILE__) << std::endl; }
GCC 14.1编译错误信息
<source>: In function 'int main()': <source>:19:34: in 'constexpr' expansion of 'best_effort_trim(std::basic_string_view<char>(((const char*)"<source>")))' <source>:11:33: error: call to consteval function 'compile_time_trim((* & s))' is not a constant expression 11 | return compile_time_trim(s); | ~~~~~~~~~~~~~~~~~^~~ In file included from <source>:1: <source>:19:34: in 'constexpr' expansion of 'best_effort_trim(std::basic_string_view<char>(((const char*)"<source>")))' <source>:11:33: in 'constexpr' expansion of 'compile_time_trim((* & s))' <source>:6:18: in 'constexpr' expansion of '(& s)->std::basic_string_view<char>::data()' /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/string_view:290:22: error: '*(const std::basic_string_view<char>*)this' is not a constant expression 290 | { return this->_M_str; } | ~~~~~~^~~~~~ Compiler returned: 1
现象说明
将constexpr const char* best_effort_trim(改为consteval const char* best_effort_trim(后,GCC编译无错误。
核心疑问
- 宏
__FILE__是编译时常量,传入constexpr函数后,参数s为何被视为非常量? - 若
s是非常量表达式,为何return compile_time_trim(s);分支仍会被求值并触发错误?
解答
1. constexpr函数参数为何不被视为常量表达式?
C++标准规定:constexpr函数的参数本身不属于常量表达式,哪怕传入的实参是编译期常量。这是因为constexpr函数需要兼容两种执行场景:编译期求值和运行时求值。
当调用best_effort_trim(__FILE__)时,__FILE__确实是编译期字符串,但s作为函数形参,是局部std::string_view对象的引用。在constexpr函数的编译期求值语境中,形参的值类别为非常量——你无法用它初始化constexpr变量,也不能传递给要求常量表达式的consteval函数。
而将best_effort_trim改为consteval函数后,函数只能在编译期执行,编译器会把实参的常量属性直接传递给形参,此时s会被当作常量表达式处理,因此compile_time_trim(s)能正常调用。
2. 非常量情况下为何consteval分支仍被求值?
if consteval是C++20的编译期分支判断,它的分支选择由调用语境决定:如果当前是编译期求值语境,就走consteval分支;如果是运行时求值语境,就走else分支。
在main中调用constexpr函数best_effort_trim时,因为实参是编译常量,编译器会尝试进行编译期求值,此时if consteval条件为真,进入return compile_time_trim(s);分支。但如前所述,constexpr函数的形参s不是常量表达式,而compile_time_trim是consteval函数,要求参数必须是常量表达式,因此触发错误。
如果传入的是运行时字符串(比如用户输入的字符串),编译器无法进行编译期求值,就会走else分支,不会调用consteval函数,也就不会报错。
内容的提问来源于stack exchange,提问作者HCSF

