CRTP实现中调用static constexpr函数出现未定义错误的原因
以下是出现问题的CRTP代码:
template<class T> struct Interface { static constexpr int func(); static constexpr int func2(); }; template<class T> struct Default : public Interface<T> { constexpr static int impl_func() { return 0; } constexpr static int impl_func2() { return Default<T>::func() + 2; // ----------- (A) } }; struct Derived : public Default<Derived> { static constexpr int impl_func2() { // NOTE: Using impl_func instead of func here compiles return func() + 4; // -------------- (B) } }; struct Derived2 : public Default<Derived2> { }; template <typename T> constexpr int Interface<T>::func() { return T::impl_func(); } template <typename T> constexpr int Interface<T>::func2() { return T::impl_func2(); } int main() { constexpr int ret = Derived::func2(); static_assert(ret == 4); constexpr int ret2 = Derived2::func2(); static_assert(ret2 == 2); }
问题现象
编译上述代码时,(A)处代码可正常编译,但(B)处代码会触发以下错误:
test3.cpp:45:19: error: constexpr variable 'ret' must be initialized by a constant expression
constexpr int ret = Derived::func2();
^ ~~~~~~~~~~~~~~~~
test3.cpp:29:16: note: undefined function 'func' cannot be used in a constant expression
return func() + 4;
将(B)替换为return impl_func() + 4可成功编译,但Derived::func()和Default<Derived>::func()均无效。疑问点在于:为何(A)处于模板类中能正常编译,而(B)不行?
核心原因分析
模板与非模板的名称查找时机差异
(A)处的模板类场景:
Default<T>是模板类,其成员函数impl_func2中的Default<T>::func()属于依赖于模板参数的名称。编译器在处理模板定义时,不会立即解析这个名称,而是延迟到模板实例化(即Default<Derived>被实例化)的时候才去查找。此时Derived类已经完全定义,Interface<Derived>::func()的模板实现也已经可见,编译器能确认这个调用符合常量表达式的要求,因此可以正常编译。(B)处的非模板类场景:
Derived是普通非模板类,它的成员函数impl_func2中的func()属于非依赖名称。编译器在处理Derived的定义时,会立刻查找func()的具体实现,但此时Interface<Derived>::func()的模板实现(写在Derived类之后)还未被编译器看到,所以编译器认为func()是未定义的,无法用于常量表达式初始化,从而报错。
为何替换为impl_func()可行?
impl_func()是基类Default<Derived>的成员函数,在Derived的定义阶段,基类的成员已经完全可见,编译器能直接找到impl_func()的定义,因此可以作为常量表达式使用。
为何显式指定作用域也无效?
即使写成Derived::func()或Default<Derived>::func(),在Derived类的定义阶段,Interface<Derived>::func()的模板实现仍然处于类定义的后面,编译器无法提前看到这个函数的具体实现,依然无法确认它是有效的常量表达式函数,所以还是会报错。
内容的提问来源于stack exchange,提问作者ktqq99

