开发者

Implicit conversion for pointer to data member vs. non-member

Most discussion I've seen about member pointers focus on conversions permitted on the type to which the member belongs. My question is about conversions on the type of the member.

struct Base{};
struct Derived : public Base{};
struct Foo{ Derived m_Derived; };

Given these declarations, the following code produces an error (MSVC 2008):

// error C2440: 'initializing' : cannot convert from 'Derived Foo::* ' to 'Base Foo::* '
Base Foo::*p = &Foo::m_Derived;

Conversion from Derived* to Base* is normally allowed - why the differen开发者_StackOverflow中文版ce here?


You have the variance backward.

Return types convert implicitly to base types (contravariance). But parameters convert implicitly to derived types (covariance), and the class type in a pointer-to-member acts as a parameter. To see this, let's apply the Liskov Substitutability Principle:

The contract of Base* is: "I will give you a Base" (when you use the * operator on me). The contract of Derived* is "I will give you a Derived, which is also a Base".

Clearly a Derived* can be used in place of a Base*. Therefore there is an implicit conversion from Derived* to Base*.

But consider the contract of a pointer-to-member.

The contract of int Base::* is: "Give me a Base and I will give you back an int" (A Derived is a Base, so those are ok too) The contract of int Derived::* is: "Give me a Derived and I will give you back an int" (But not any old Base will do, it must be a Derived)

Imagine that you have a Base which is not a Derived. It will work nicely when dereferencing an int Base::*, but cannot be used with an int Derived*).

However, if you have a Derived, you can use it to dereference both int Base::* and int Derived::*. Therefore there is an implicit conversion from int Base::* to int Derived::*

Argh, I did what you said and analyzed the type that the member belongs to.

The LSP still works though. And I agree that the conversion should be legal, at least according to type safety. The contract is "Give me a Foo and I will give you a Derived", which clearly you can use to get from Foo to Base by composing it with an implicit conversion. So it's safe. DeadMG is probably on the right track pointing out the potential complication with the relation location of the base subobject, especially in virtual inheritance. But pointer-to-members deal with these problems on the LHS of the dereference operator, so they could for the result as well.

Final answer is probably simply that the standard doesn't require that the conversion is legal.


@Ben Voigt: Why did you strike out your correct answer?

Safe pointer conversions are contra-variant, meaning, you can upcast safely but not downcast.

Safe pointer to member conversions are co-variant, meaning you can downcast safely, but not upcast.

The reason is easy to see when you think about it. Suppose you have a pointer to a member of Base, that's an offset into Base. Then if the complete object is a Derived, what's the offset of that same member into Derived? Usually its exactly the same offset: certainly it will be if the pointers to Base and Derived would be equal addresses. If you have multiple inheritance and virtual bases things get a bit trickier :)

In fact, the OP's example code is the perfect example of why up casts are not safe: the offset of a member of Derived class could be applied to any Base, and point off the end of the base subobject, which is only OK if it is actually a Derived and not say, a Derived2.


Interesting question. Data pointers are so rarely used, I'm quite unfamiliar with the rules.

However, I'm going to put money that it's because of multiple inheritance. If Base and Derived use virtual inheritance, the compiler can't know at compile-time the offset of Base within any given Derived, making a compile-time offset impossible, and it would be far too much work to legalize it for non-virtual inheritance.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜