关于C++ constexpr函数两种写法合法性差异的技术问询
核心差异点
两段代码的本质区别在于**constexpr变量的初始化规则与函数参数在不同语境下的常量表达式可用性**:
第一段合法代码分析
constexpr int incr(int k1) { return k1 + 5; } constexpr int foo(int k) // runs in compile time { return incr(k); } int main() { constexpr int x = 5; constexpr int y = foo(4); }
当foo(4)作为constexpr int y的初始化器时,整个调用处于常量求值语境:参数k被绑定到常量值4,此时调用incr(k)的过程是在常量求值中完成的——k的值在求值时是确定的,且incr是符合要求的constexpr函数,因此整个表达式满足常量表达式的要求。
第二段非法代码分析
constexpr int incr(int k1) { return k1 + 5; } constexpr int foo(int k) // runs in compile time { constexpr int x = incr(k); // k: not a constant expression return x; } int main() { constexpr int x = 5; constexpr int y = foo(4); }
这里的错误出在constexpr int x = incr(k);这条语句:constexpr变量必须用常量表达式初始化,而此处的k是foo的函数形参——在foo的定义语境中,k并不是一个“可用于常量表达式的实体”,即便调用foo(4)时k会被绑定到常量值,也无法满足constexpr变量初始化对表达式的语境要求。
引用C标准条款(以C20 N4861为例)
constexpr变量的初始化要求([dcl.constexpr]/9):constexpr变量必须被初始化。若通过构造函数调用初始化,则该调用必须是常量表达式([expr.const])。否则,或者若constexpr变量通过lambda表达式初始化,初始化器必须是常量表达式。常量表达式的限制([expr.const]/4):
一个表达式是核心常量表达式,当且仅当对其求值时不会触发以下情况:
...
(4.2) 引用了不可用于常量表达式的变量的id表达式;
...可用于常量表达式的变量定义([expr.const]/11):
变量或临时对象可用于常量表达式,当且仅当它是:
(11.1) 可用于常量表达式的引用类型变量;
(11.2) 用常量表达式初始化或默认初始化的constexpr类型变量;
...
函数参数k不属于上述任何一种“可用于常量表达式”的情况,因此在constexpr int x = incr(k);的语境中,incr(k)不是合法的常量表达式,无法完成constexpr变量的初始化。
而第一段代码中,return incr(k);的求值是在foo被作为常量表达式调用的过程中进行的,此时参数k被绑定到常量值,属于常量求值过程中允许的参数使用场景([expr.const]/6):
若对
constexpr函数的调用满足常量表达式的要求,且该函数是constexpr函数,则该调用是常量表达式。
内容的提问来源于stack exchange,提问作者SomeoneWithPassion

