派生类调用基类模板constexpr函数报C2131错误的原因咨询
关于MSVC下constexpr成员函数调用的C2131报错分析
问题场景
在MSVC /std:c++latest 编译环境下,以下代码触发C2131错误(表达式未计算为常量):
template <typename... Ts> struct A { template <typename C> constexpr auto proc() noexcept { return C{ }; } constexpr size_t size() noexcept { return sizeof...(Ts); } }; template <typename... Ts> struct B : public A<Ts...> { using base_t = A<Ts...>; template <typename... Cs> constexpr auto example_prog_1() noexcept { constexpr size_t tmp = base_t::size(); // error C2131 : its not constexpr return tmp + sizeof...(Cs); } template <typename... Cs> constexpr auto example_prog_0() noexcept { return example_prog_1<decltype(base_t::template proc<Cs>())...>(); } }; int main() { B<int, int, int, int> obj0 { }; constexpr size_t result0 = obj0.example_prog_0<char, char, char, char>(); }
可编译的修改方案
有两种修改方式可以让代码正常编译:
方式1:创建派生类对象调用size()
修改example_prog_1为以下代码:
template <typename... Cs> constexpr auto example_prog_1() noexcept { B dup{ }; constexpr size_t tmp = dup.size(); // error none : its fine return tmp + sizeof...(Cs); }
方式2:使用C++23显式对象参数调用size()
添加this auto self参数后调用:
template <typename... Cs> constexpr auto example_prog_1(this auto self) noexcept { constexpr size_t tmp = self.size(); return tmp + sizeof...(Cs); }
报错原因解析
核心差异在于编译器对无实例调用和绑定实例调用的常量表达式判定逻辑不同:
- 原代码中
base_t::size()属于「无实例的非静态成员函数静态调用」——虽然size()是constexpr且只依赖模板参数,但MSVC的常量求值器认为这种调用没有绑定到具体对象实例,无法确定其编译期值,因此判定不满足常量表达式要求。 - 而创建
B dup{}对象调用dup.size(),或者用this auto self调用时,调用绑定到了具体的类实例上。此时编译器能明确识别该实例对应的模板参数Ts...是确定的(比如B<int,int,int,int>),可以直接在编译期计算size()的返回值,符合constexpr的要求。
从C++标准角度看,原代码的base_t::size()调用其实应该是合法的常量表达式,这更像是MSVC编译器的实现细节限制——对无实例的constexpr成员函数调用的常量性判定不够宽松,而通过实例调用时则能正确识别。
内容的提问来源于stack exchange,提问作者WsabiWafer
相关产品推荐
相关产品推荐

