基类成员指针编译分歧问询:C++标准立场与问题性质判定(UB/未文档化行为/编译器bug)
问题拆解:私有继承+using声明下的成员指针行为与编译器差异
让我们结合C++标准规则,一步步分析你遇到的问题,明确哪些是标准规定的行为,哪些是编译器实现差异:
1. 核心标准规则梳理
1.1 成员指针的类型推导
根据C++标准中**[expr.unary.op]/3**的规定:
表达式
&X::m的类型是「指向C的成员m的指针」,其中C是m最初被声明的类(也就是最直接的定义类)——除非X自身显式声明了同名成员m(using声明不算重新声明,只是把基类成员引入派生类作用域)。
对应你的测试代码:
baz1、baz2、baz3都没有自己声明foo,所以&baz1::foo、&baz2::foo、&baz3::foo的类型都是int (bar::*)(),而非指向各自派生类的成员指针。
1.2 私有继承与using声明的访问权限
根据**[namespace.udecl]/19**:
using声明引入的成员,其访问权限由声明所在的作用域决定,而非原成员的访问级别。
对于baz3:
- 它私有继承
bar,原本bar::foo在baz3中是私有访问;但using bar::foo;写在struct baz3的public作用域(struct默认成员为public),所以baz3::foo对外部代码是可访问的——你可以合法调用baz3{}.foo(),同样,取成员指针&baz3::foo也是合法的,因为标准允许通过可访问的using声明获取基类成员的指针。
2. 编译器差异的本质
2.1 MSVC的编译错误:属于编译器bug
MSVC在实例化foo_address<U>和foo_type<U>时,错误地忽略了using声明带来的访问权限提升,认为&baz3::foo是访问私有基类的成员,从而抛出硬错误。这不符合C++标准的规定,属于编译器实现缺陷,而非未定义行为。
2.2 Clang的正确行为
Clang正确识别了using声明的作用:它确认baz3::foo是可访问的,并且正确推导&baz3::foo的类型为int (bar::*)(),因此可以正常实例化模板并通过编译。
3. 测试代码中的类型一致性验证
你的测试代码里,std::is_same_v<decltype(&t::foo), foo_type<t>>应该始终返回true:
foo_type<U>是std::remove_const_t<decltype(foo_address<U>)>,而foo_address<U>就是&U::foo,两者的类型完全一致。- 对于
baz1和baz2,所有编译器都应该输出true;对于baz3,Clang输出true,而MSVC因编译错误无法执行,这进一步证明了MSVC的实现问题。
4. 权威标准参考条目
- [expr.unary.op]/3:定义了成员指针表达式的类型推导规则与合法性要求
- [namespace.udecl]/19:明确了
using声明的访问权限继承规则 - [temp.inst]/8:规定模板实例化时的访问检查时机(必须在实例化阶段进行,而非模板定义阶段)
内容的提问来源于stack exchange,提问作者Red.Wave
相关产品推荐
相关产品推荐

