为何constexpr模板函数调用非constexpr函数可编译而非模板版不行?
foo能编译,而非模板constexpr函数bar不行? 咱们先拆解你遇到的场景,结合代码和报错来分析:
首先看你给出的核心代码片段:
template<class T> void set(T& x){ x++; } template<class T> constexpr void foo(T& x){ set<T>(x); } constexpr void bar(int& x){ set<int>(x); } void bar(){ int x = 5; foo(x); bar(x); }
编译器报错的是constexpr void bar(int&)这个函数——它是非模板的constexpr函数,编译器在解析它的定义时就会严格执行constexpr的规则:函数内部的所有操作必须能在编译期完成。而set<int>不是constexpr函数,所以直接触发错误,这完全符合预期。
而foo是constexpr模板函数,这里有两个关键规则在起作用,让它能正常编译:
1. 模板的延迟实例化特性
模板函数的定义本身只是一个"蓝图",编译器不会在看到它的时候就检查内部逻辑是否符合constexpr要求——只有当它被实际调用(或显式实例化)时,才会根据传入的模板参数生成具体的函数实例。也就是说,foo的定义本身不会被提前校验,只有你调用foo(x)(也就是实例化foo<int>)时,编译器才会去检查这个具体实例的合法性。
2. constexpr模板函数的"弹性规则"
C++标准对constexpr模板函数的要求是:只要存在至少一组模板参数,能让函数实例满足constexpr的所有要求,整个模板就是合法的。对于那些不满足constexpr要求的参数实例(比如这里的foo<int>,因为调用了非constexpr的set<int>),这个实例会自动退化为普通的非constexpr函数——它仍然可以被正常调用,只是不能用于编译期常量表达式的场景。
回到你的代码,虽然foo<int>不能作为constexpr函数使用,但因为foo是模板,编译器允许它存在这样的实例,只要理论上存在其他模板参数能让它成为合法的constexpr函数(比如如果有一个constexpr版本的set特化,或者传入支持编译期操作的类型),所以foo的定义本身是合法的,调用它也没问题。
补充:关于编译优化的差异
你后来补充的代码里,foo被优化为直接inline调用set,而foo2保留了独立的函数调用,这是因为constexpr函数(包括模板实例)隐含了inline属性,编译器会优先尝试将其inline展开;而普通的foo2模板函数没有这个隐含属性,默认情况下编译器可能不会inline它(除非手动添加inline或开启更高优化等级)。
看你给出的编译结果:
void set<int>(int volatile&): ldr r3, [r0] add r3, r3, #1 str r3, [r0] bx lr void foo2<int>(int&): push {r4, lr} bl void set<int>(int volatile&) pop {r4, lr} bx lr bar(): push {r4, lr} sub sp, sp, #8 add r4, sp, #8 mov r3, #5 str r3, [r4, #-4]! mov r0, r4 bl void set<int>(int volatile&) // foo<int>被inline直接调用set mov r0, r4 bl void foo2<int>(int&) // foo2<int>保留了函数调用 add sp, sp, #8 pop {r4, lr} bx lr
这里的差异完全是inline属性导致的优化策略区别,和函数是否为constexpr模板没有功能上的关联。
内容的提问来源于stack exchange,提问作者Alex R.

