CRTP模式下经基类访问派生类constexpr成员变量的报错解析
CRTP场景下基类访问派生类constexpr成员的编译问题
复现路径
实际开发中基于CRTP模式实现基类通用逻辑时,尝试在基类中访问派生类定义的constexpr静态成员变量,写出的原始代码如下:
template <typename Der> struct Base { constexpr std::size_t getsize() { constexpr const auto &b = static_cast<Der*>(this)->arr; return b.size(); //return static_cast<Der*>(this)->arr.size(); // 这行写法可正常编译 } }; struct Derived : Base<Derived> { static constexpr std::array<int, 10> arr = {}; }; int main(){ Derived d; return d.getsize(); }
上述代码触发的编译报错信息如下:
<source>:11:31: error: constexpr variable 'b' must be initialized by a constant expression constexpr const auto &b = static_cast<Der*>(this)->arr; ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:24:14: note: in instantiation of member function 'Base<Derived>::getsize' requested here return d.getsize(); ^ <source>:11:53: note: use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function constexpr const auto &b = static_cast<Der*>(this)->arr; ^ 1 error generated. Compiler returned: 1
经过测试,只要将getsize函数内对局部变量b声明的constexpr关键字移除,代码即可正常编译运行,修改后的函数逻辑为:
auto &b = static_cast<Der*>(this)->arr; return b.size();
报错产生的本质原因
- C++标准对
constexpr修饰的局部变量有强制约束:变量的初始化表达式必须是独立合法的常量表达式,编译器会单独对这个初始化表达式做严格的常量表达式合法性校验,这个校验流程不属于外层constexpr成员函数的求值上下文。 - 常量表达式的合法性规则中,
this指针的使用存在严格场景限制:只有当表达式是某个constexpr成员函数的求值组成部分时,this的使用才是合规的。但在单独校验constexpr局部变量的初始化表达式时,这个校验上下文不被认定为constexpr成员函数的求值环节,因此表达式中对this的引用会直接触发合法性错误,和编译器给出的note提示完全对应。 - 本次问题中的
arr本身是符合要求的静态constexpr常量,报错根源不在于arr的定义本身,而在于访问arr的路径依赖了this指针,这个依赖在constexpr局部变量的独立校验环节是不被允许的。 - 代码注释中标注的直接返回
static_cast<Der*>(this)->arr.size()的写法之所以能正常编译,是因为这段表达式作为整个constexpr成员函数的返回值存在,它的求值全程被纳入外层constexpr成员函数的调用流程,符合this指针的使用场景要求,没有触发独立的常量表达式校验环节,因此不会报错。 - 如果确实需要保留局部变量的constexpr修饰,只需要去掉对this的依赖,直接通过类名访问静态成员即可:写成
constexpr const auto &b = Der::arr;,这个访问路径完全不涉及this指针,天然符合常量表达式的要求,不会触发编译错误。
移除constexpr后代码正常运行的底层逻辑
- 去掉局部变量
b前面的constexpr修饰后,b变为普通的常量引用局部变量,C++对普通局部变量的初始化表达式没有“必须为常量表达式”的强制校验要求,只要表达式语法合法、可正常完成取值流程即可通过编译。 - 代码中通过
static_cast<Der*>(this)访问arr的操作,本质是访问类的静态成员:静态成员不依附于类的实例对象存储,完全不依赖this指向的实例内存,哪怕转型后的指针值没有指向合法的Derived实例,只要能拿到静态成员的地址,整个访问流程就是合法的,不会产生未定义行为。 - 这种写法下,代码依然可以在编译期完成求值(因为最终取值来自静态constexpr成员的size值),但因为没有强制要求局部变量本身必须是constexpr属性,也就不会触发之前针对constexpr局部变量初始化表达式的严格校验流程,自然消除了编译错误。
内容的提问来源于stack exchange,提问作者user3882729
相关产品推荐
相关产品推荐

