C++20中virtual constexpr函数不被识别为常量?MSVC编译问题解析
C++20 constexpr虚函数编译问题及合规实现
问题背景
我编写了一段使用C++20 virtual constexpr特性的代码,可通过Clang正常编译,但在MSVC中编译失败。
代码示例
enum legCount { APODA = 0, BIPEDAL = 2, QUADRUPEDAL = 4, }; class Animal { public: constexpr virtual legCount getLegCount() = 0; }; class Tiger : public Animal { public: constexpr legCount getLegCount() { return QUADRUPEDAL; } void manipulateLegsArray() { unsigned char arr[getLegCount()]; } };
MSVC报错信息
D:\Projects\temp\demo\main.cpp(17,32): error C2131: expression did not evaluate to a constant [D:\Projects\temp\out\demo\demo.vcxproj]
D:\Projects\temp\demo\main.cpp(17,21): message : failure was caused by a read of a variable outside its lifetime [D:\Projects\temp\out\demo\demo.vcxproj]
D:\Projects\temp\demo\main.cpp(17,21): message : see usage of 'this' [D:\Projects\temp\out\demo\demo.vcxproj]
问题解答
1. MSVC的报错是否符合C++标准?原因是什么?
MSVC的报错符合C++标准,具体原因如下:
- 虽然
Tiger::getLegCount()是constexpr函数,但在manipulateLegsArray()中调用它时,是通过this指针触发的动态绑定调用。 - 根据C++标准,常量表达式中不能包含对
this指针的使用(除非是在constexpr成员函数内部,且调用上下文本身是常量表达式)。而manipulateLegsArray()不是constexpr函数,此时this指向的对象生命周期始于运行期,无法在编译期被视为常量。 - 虚函数的动态绑定依赖运行期的虚表查找,这一过程无法在编译期完成,因此
getLegCount()在这里的调用结果不能作为常量表达式,自然无法用于变长数组的大小声明。
Clang的编译通过属于编译器扩展行为,并不符合C++标准的严格要求。
2. 合规实现抽象基类强制派生类实现constexpr函数的方法
要实现“抽象基类要求所有派生类必须实现constexpr函数”的需求,以下是两种合规方案:
方案一:纯虚函数+静态constexpr成员(双重约束)
enum legCount { APODA = 0, BIPEDAL = 2, QUADRUPEDAL = 4, }; class Animal { public: // 纯虚函数,强制派生类实现运行期接口 virtual legCount getLegCount() const = 0; // 声明静态constexpr接口,派生类必须实现自己的版本 static constexpr legCount staticLegCount() = delete; }; class Tiger : public Animal { public: // 实现纯虚函数,复用静态constexpr的结果 legCount getLegCount() const override { return staticLegCount(); } // 实现静态constexpr函数,满足编译期常量需求 static constexpr legCount staticLegCount() { return QUADRUPEDAL; } void manipulateLegsArray() { // 使用静态constexpr函数的结果,编译期确定数组大小 unsigned char arr[staticLegCount()]; } };
- 纯虚函数
getLegCount()确保派生类必须实现运行期接口; - 基类的
staticLegCount()被声明为delete,强制派生类必须实现自身的静态constexpr版本; - 编译期使用
staticLegCount()获取常量值,运行期可通过虚函数调用统一接口。
方案二:CRTP实现编译期绑定
enum legCount { APODA = 0, BIPEDAL = 2, QUADRUPEDAL = 4, }; // 抽象基类模板 template<typename Derived> class Animal { public: // 编译期直接调用派生类的constexpr函数 constexpr legCount getLegCount() const { return Derived::staticLegCount(); } // 纯虚函数,确保基类无法直接实例化 virtual void dummy() = 0; }; class Tiger : public Animal<Tiger> { public: // 派生类必须实现的constexpr静态函数 static constexpr legCount staticLegCount() { return QUADRUPEDAL; } void manipulateLegsArray() { unsigned char arr[staticLegCount()]; } // 实现基类的纯虚函数 void dummy() override {} };
- CRTP让基类在编译期直接调用派生类的
staticLegCount(),避免了动态绑定; - 基类中的纯虚函数
dummy()确保Animal无法被直接实例化,保持抽象性; - 派生类必须实现
staticLegCount(),否则编译会报错,达到强制约束的目的。
内容的提问来源于stack exchange,提问作者user_5309734
相关产品推荐
相关产品推荐

