简答:
我相信即使Derived 实际上派生自Base,编译器也可以将Base::* 转换为Derived::*。为此,指向成员的指针需要记录的不仅仅是偏移量。它还需要通过某种类型擦除机制来记录原始指针的类型。
所以我的猜测是委员会认为这对于一个很少使用的功能来说太过分了。此外,使用纯库功能可以实现类似的功能。 (见长答案。)
长答案:
我希望我的论点在某些极端情况下没有缺陷,但我们开始吧。
本质上,指向成员的指针记录了成员相对于类开头的偏移量。考虑:
struct A { int x; };
struct B : virtual A { int y; };
struct C : B { int z; };
void print_offset(const B& obj) {
std::cout << (char*) &obj.x - (char*) &obj << '\n';
}
print_offset(B{});
print_offset(C{});
在我的平台上,输出是12 和16。这表明a相对于obj的地址的偏移量取决于obj的动态类型:12如果动态类型是B和16如果它是C。
现在考虑 OP 的例子:
int A::*p = &A::x;
int B::*pb = p;
正如我们所见,对于静态类型B 的对象,偏移量取决于其动态类型,并且在上面的两行中,没有使用B 类型的对象,因此没有动态类型可以从中获取偏移量。
但是,要取消引用指向成员对象的指针是必需的。编译器不能获取当时使用的对象来获得正确的偏移量吗?或者,换句话说,偏移量计算是否可以延迟到我们评估obj.*pb(其中obj 是静态类型B)?
在我看来这是可能的。将obj 转换为A& 并使用pb 中记录的偏移量(从p 读取)来获得对obj.x 的引用就足够了。为此,pb 必须“记住”它是从 int A::* 初始化的。
这里是一个模板类ptr_to_member 的草稿,它实现了这个策略。专业化ptr_to_member<T, U> 应该与T U::* 类似地工作。 (请注意,这只是一个可以通过不同方式改进的草案。)
template <typename Member, typename Object>
class ptr_to_member {
Member Object::* p_;
Member& (ptr_to_member::*dereference_)(Object&) const;
template <typename Base>
Member& do_dereference(Object& obj) const {
auto& base = static_cast<Base&>(obj);
auto p = reinterpret_cast<Member Base::*>(p_);
return base.*p;
}
public:
ptr_to_member(Member Object::*p) :
p_(p),
dereference_(&ptr_to_member::do_dereference<Object>) {
}
template <typename M, typename O>
friend class ptr_to_member;
template <typename Base>
ptr_to_member(const ptr_to_member<Member, Base>& p) :
p_(reinterpret_cast<Member Object::*>(p.p_)),
dereference_(&ptr_to_member::do_dereference<Base>) {
}
// Unfortunately, we can't overload operator .* so we provide this method...
Member& dereference(Object& obj) const {
return (this->*dereference_)(obj);
}
// ...and this one
const Member& dereference(const Object& obj) const {
return dereference(const_cast<Object&>(obj));
}
};
它的使用方法如下:
A a;
ptr_to_member<int, A> pa = &A::x; // int A::* pa = &::x
pa.dereference(a) = 42; // a.*pa = 42;
assert(a.x == 42);
B b;
ptr_to_member<int, B> pb = pa; // int B::* pb = pa;
pb.dereference(b) = 43; // b*.pb = 43;
assert(b.x == 43);
C c;
ptr_to_member<int, B> pc = pa; // int B::* pc = pa;
pc.dereference(c) = 44; // c.*pd = 44;
assert(c.x == 44);
很遗憾,单独ptr_to_member 并不能解决Steve Jessop 提出的问题:
在与 TemplateRex 讨论之后,这个问题是否可以简化为“为什么我不能做 int B::*pb = &B::x;?这不仅仅是你不能转换 p:你不能有一个指向成员的指针,指向虚拟基中的成员。
原因是表达式&B::x 应该只记录x 与B 开头的偏移量,正如我们所见,这是未知的。为了完成这项工作,在意识到B::x 实际上是虚拟基础A 的成员之后,编译器需要从&A::X 创建类似于ptr_to_member<int, B> 的东西,它“记住”在构造时看到的A时间并记录x与A开头的偏移量。