Why does the C++ spec. prohibit downcasting through non-publicinheritance?

E

edam

I wondered if anyone had any insight as to why the C++ spec. was
designed with the following behaviour. I've been thinking about it all
morning and neither myself nor my colleagues are able to come up with
a
reason.

If a class is derived from a protected base class, then dynamic_cast
will not allow you to cast from a pointer or reference to the base
class to the deriving class. Here's some code to demonstrate the
point:

struct B {
virtual ~B() {};
};

struct D : protected B {
};

void main() {
D d;
B &b = dynamic_cast< B & >( d ); // compiler error
B &b2 = ( B & )d; // circumventing protection
D &d2 = dynamic_cast< D & >( b2 ); // run-time error
}

The error my compiler (GCC 4.4.3) gave me was "error: ‘B’ is an
inaccessible base of ‘D’" and the run-time error was a thrown
std::bad_cast. Both of these seem reasonable, since main() should not
have access to D's protected base. So far so good. But now consider
this code:

struct B {
virtual ~B() {};
};

struct D : protected B {
void foo() {
B &b = dynamic_cast< B & >( *this ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
friend class F;
};

struct F {
void foo( D &dref ) {
B &b = dynamic_cast< B & >( dref ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
};

void main() {
D d;
d.foo();
F f;
f.foo( d );
}

Here, we successfully upcast from a derived class reference to a
protected base class reference in two places: from a member function
of
the derived class and from a member function of a friend class.

We also attempt to downcast from a protected base class reference to a
derived class reference in the same two places--an action that would
succeed if we weren't using protected inheritance. Both attempts fail
with std::bad_cast thrown.

It is my understanding that, where public inheritance represents the
"is-a" relationship, protected and private inheritance are another way
of expressing the "has-a" relationship in OOP; an alternative to using
member variables. In this sense, by using protected inheritance, you
are hiding the implementation details of the derived class from users
of that class. Those implementation details should only be available
to
methods of D and any classes that you derive from D, as is the case
with protected member functions and variables.

Given this, I cannot understand why a member function of the derived
class or of a friend class can not downcast a protected base class
reference to a derived class reference specifically because we are
using protected inheritance. Surely, if the intention of protected and
private inheritance is to protect and make private the implementation
of a class, you shouldn't be hiding that implementation from the class
its self and its friends!

Also, by way of comparison, consider an implementation where B is
a protected member variable of D, instead of as its protected base.
This would still represent the "has-a" relationship (D has a B). If
this were the case, both D's member functions and D's friends would
have access to its B member variable (its implementation). This seems
to further indicate that downcasting to a derived class from its
protected base should be allowed in member functions and friends of
the derived class.

I was wondering if anyone had any ideas as to why the design of C++
seems to prohibit it in all cases? I was also wondering if anyone
could
suggest a method of performing this downcast from a member/friend
without having to resort to reinterpret_cast?

Thanks,

Tim.
 
K

Krice

I was wondering if anyone had any ideas as to why the design of C++
seems to prohibit it in all cases?

Casting in context of classes is always a programmer's design
error. The way classes work in OOP is something where everything
happens for a reason. Remember: most problems in programming are
derived from the thinking that you know better than the creators
of the language.
 
A

Alf P. Steinbach

* edam:
void main() {

Don't do that. It's one more character to type and non-standard. main must
return int.

[...] I cannot understand why a member function of the derived
class or of a friend class can not downcast a protected base class
reference to a derived class reference specifically because we are
using protected inheritance.

Consider a class hierarchy Base and SomeoneElsesDerived with protected
inheritance from Base.

If you could downcast from Base to your own protected class then you could
upcast from SomeoneElsesDerived and down to your own class, gaining access to
Base stuff in that SomeoneElsesDerived object.

'protected' would then not mean anything[1].


Cheers & hth.,

- Alf


Notes:
[1] As nearly always in C++ the protection can be circumvented. And in
particular this protection can be circumvented by using member pointers, without
any explicit casting. Which is a bit dangerous.
 
P

Paul Bibbings

Alf P. Steinbach said:
* edam:
[...] I cannot understand why a member function of the derived
class or of a friend class can not downcast a protected base class
reference to a derived class reference specifically because we are
using protected inheritance.

Consider a class hierarchy Base and SomeoneElsesDerived with protected
inheritance from Base.

If you could downcast from Base to your own protected class then you
could upcast from SomeoneElsesDerived and down to your own class,
gaining access to Base stuff in that SomeoneElsesDerived object.

'protected' would then not mean anything

In order for this to be understood as an answer to the OP's question, it
would need to be the case that described upcast-plus-downcast would be
okay where protected inheritance was not used. Are you suggesting that
the following would `succeed'?:

struct Base { virtual ~Base() { } };

struct SomeoneElsesDerived : public Base { };

struct MyDerived : public Base { };

int main()
{
SomeoneElsesDerived sed;
Base& b = dynamic_cast<Base&>(sed);
MyDerived& md = dynamic_cast<MyDerived&>(b);
}

As it clearly would not, can you expand on how an example that fails in
all instances can be used as an argument against what the OP expects
should happen in a specific one?

Regards

Paul Bibbings
 
P

Paul Bibbings

Alf P. Steinbach said:
Example of how the dynamic failure of dynamic_cast corresponds to
compilation failure for static_cast:


<code>
class Base
{
public:
virtual ~Base() {}
};

class Derived: protected Base
{
public:
void foo( Base& o )
{
static_cast<Derived&>( o );
}
};

void bar( Base& o )
{
static_cast<Derived&>( o ); // Nope.
}

int main() {}
</code>


For static_cast this protects you against using knowledge of the
derivation from Base to treat Derived objects as Base objects or vice
versa. The protected derivation says that it's an implementation
detail, and e.g. in the future might be replaced by a Base* data
member, or a different class, whatever.

If the static_cast labeled "Nope" above is replaced with dynamic_cast,
then it compiles.
But it then compiles almost no matter which class is specified instead
of Derived&, e.g. a completely unrelated class FooBar& -- and this
is perhaps what baffled the OP?

I wonder if even this captures the OP's original question.
edam said:
Given this, I cannot understand why a *member function* of the derived
class or of a friend class can not downcast a protected base class
reference to a derived class reference specifically because we are
using protected inheritance. Surely, if the intention of protected and
private inheritance is to protect and make private the implementation
of a class, you shouldn't be hiding that implementation from the class
its self and its friends! [my emphasis]

Reading this, the key question appears to be specifically about the
runtime failure of attempting a dynamic_cast from base reference to
derived reference *in a member function of the derived class*, as in:

19:30:32 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $cat dyn_cast_ex.cpp
// dyn_cast_ex.cpp

#include <iostream>
#include <typeinfo>

class Base
{
public:
virtual ~Base() {}
};

class Derived: protected Base
{
public:
void foo( ) // member function
{
Base& b = dynamic_cast<Base&>( *this ); // OK
try {
Derived& d = dynamic_cast<Derived&>( b ); // why not OK here?
} catch (std::bad_cast) {
std::cout << "Oopsie!\n";
}
}
};

int main() {
Derived d;
d.foo( );
}

19:30:35 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $g++ -o dyn_cast_ex dyn_cast_ex.cpp

19:30:54 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $./dyn_cast_ex
Oopsie!

I have to be honest and say that, after some considerable searching, I
have failed in locating a specific reference (or chain of references) in
the standard that specifically mandates against this, *in this specific
instance*.

Regards

Paul Bibbings
 
P

Paul Bibbings

Paul Bibbings said:
I have to be honest and say that, after some considerable searching, I
have failed in locating a specific reference (or chain of references) in
the standard that specifically mandates against this, *in this specific
instance*.

Actually, it would be silly to say that I have not been able to find the
reference, since it is clearly covered by the following, in relation to
the form:

dynamic_cast<T>(v)

[expr.dynamic.cast] §5.2.7/8:

"If C is the class type to which T points or refers, the run-time
check logically executes as follows:

- If, in the most derived object pointed (referred) to by v, v
points (refers) to a *public* base class subobject of a C object,
and if only one object of type C is derived from the subobject
pointed (referred) to by v the result points (refers) to that C
object.

- // ... (re: upcasting equivalent)

- *Otherwise*, the run-time check fails."

Regards

Paul Bibbings
 
P

Paul Bibbings

edam said:
But now consider
this code:

struct B {
virtual ~B() {};
};

struct D : protected B {
void foo() {
B &b = dynamic_cast< B & >( *this ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
friend class F;
};

struct F {
void foo( D &dref ) {
B &b = dynamic_cast< B & >( dref ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
};

void main() {
D d;
d.foo();
F f;
f.foo( d );
}

Here, we successfully upcast from a derived class reference to a
protected base class reference in two places: from a member function
of
the derived class and from a member function of a friend class.

We also attempt to downcast from a protected base class reference to a
derived class reference in the same two places--an action that would
succeed if we weren't using protected inheritance. Both attempts fail
with std::bad_cast thrown.

I've been having a look at this again, having *thought* that I'd
deciphered from the standard what I should expect to happen in relation
to your code here. That is /should/ happen as your results (for
gcc-4.4.3) appear to indicate probably surprised me as much as it did
you.

What surprises me even more, having recognised that there have been a
number of bugs in gcc over how access control is handled (though none
specifically in relation to use of static_cast, as I could find), I went
back to try a version of your code in the most recent release, half
expecting - from my reading of [expr.dynamic.cast] §5.2.7/8 - that it
would fail even the upcasts, only to find something very different.

If we take the code above and add the headers <iostream> and <typeinfo>,
correct the return on main and add a couple of debug print statements
there, we get:

21:23:34 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $cat dyn_cast_ex1.cpp
// file: dyn_cast_ex1.cpp

#include <iostream>
#include <typeinfo>

struct B {
virtual ~B() {};
};

struct D : protected B {
void foo() {
B &b = dynamic_cast< B & >( *this ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
friend class F;
};

struct F {
void foo( D &dref ) {
B &b = dynamic_cast< B & >( dref ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
};

int main() {
D d;
try {
d.foo();
} catch (std::bad_cast) {
std::cout << "std::bad_cast from d.foo()\n";
}
F f;
try {
f.foo( d );
} catch (std::bad_cast) {
std::cout << "std::bad_cast from f.foo(D)\n";
}
}

21:23:42 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $i686-pc-cygwin-g++-4.4.3 -o
dyn_cast_ex1 dyn_cast_ex1.cpp |
|
21:24:27 Paul Bibbings@JIJOU |
/cygdrive/d/CPPProjects/CLCPP $./dyn_cast_ex1 |
std::bad_cast from d.foo() // fail <---+
std::bad_cast from f.foo(D) // fail <---+

21:24:33 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $i686-pc-cygwin-g++-4.5.0 -o
dyn_cast_ex1 dyn_cast_ex1.cpp |
|
21:25:01 Paul Bibbings@JIJOU |
/cygdrive/d/CPPProjects/CLCPP $./dyn_cast_ex1 |
// OK? <---+
21:25:07 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $

So. gcc-4.5.0 doesn't regard there to be a problem here at all, and I'm
.... confused now.

Regards

Paul Bibbings
 
P

Paul Bibbings

Paul Bibbings said:
Paul Bibbings said:
I have to be honest and say that, after some considerable searching, I
have failed in locating a specific reference (or chain of references) in
the standard that specifically mandates against this, *in this specific
instance*.

Actually, it would be silly to say that I have not been able to find the
reference, since it is clearly covered by the following, in relation to
the form:

dynamic_cast<T>(v)

[expr.dynamic.cast] §5.2.7/8:

"If C is the class type to which T points or refers, the run-time
check logically executes as follows:

- If, in the most derived object pointed (referred) to by v, v
points (refers) to a *public* base class subobject of a C object,
and if only one object of type C is derived from the subobject
pointed (referred) to by v the result points (refers) to that C
object.

- // ... (re: upcasting equivalent)
^^^^^^^^^
Correction: crosscasting
 
P

Paul Bibbings

Paul Bibbings said:
If we take the code above and add the headers <iostream> and <typeinfo>,
correct the return on main and add a couple of debug print statements
there, we get:

21:23:34 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $cat dyn_cast_ex1.cpp
// file: dyn_cast_ex1.cpp

#include <iostream>
#include <typeinfo>

struct B {
virtual ~B() {};
};

struct D : protected B {
void foo() {
B &b = dynamic_cast< B & >( *this ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
friend class F;
};

struct F {
void foo( D &dref ) {
B &b = dynamic_cast< B & >( dref ); // upcast ok here
D &d = dynamic_cast< D & >( b ); // runtime error
}
};

int main() {
D d;
try {
d.foo();
} catch (std::bad_cast) {
std::cout << "std::bad_cast from d.foo()\n";
}
F f;
try {
f.foo( d );
} catch (std::bad_cast) {
std::cout << "std::bad_cast from f.foo(D)\n";
}
}

21:23:42 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $i686-pc-cygwin-g++-4.4.3 -o
dyn_cast_ex1 dyn_cast_ex1.cpp |
|
21:24:27 Paul Bibbings@JIJOU |
/cygdrive/d/CPPProjects/CLCPP $./dyn_cast_ex1 |
std::bad_cast from d.foo() // fail <---+
std::bad_cast from f.foo(D) // fail <---+

21:24:33 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $i686-pc-cygwin-g++-4.5.0 -o
dyn_cast_ex1 dyn_cast_ex1.cpp |
|
21:25:01 Paul Bibbings@JIJOU |
/cygdrive/d/CPPProjects/CLCPP $./dyn_cast_ex1 |
// OK? <---+
21:25:07 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $

So. gcc-4.5.0 doesn't regard there to be a problem here at all, and I'm
... confused now.

Regards

Paul Bibbings

This is completely bogus. gcc-4.5.0 handles this the same way that
gcc-4.4.3 does, and both in accordance with [expr.dynamic.cast].
Having not compiled with the -static flag, my runtime in the second
instance was picking up the wrong dynamic libraries. Building with
-static, or setting the path appropriately, gives the same results
for gcc-4.5.0 as gcc-4.4.3, in relation to the above code.

Regards

Paul Bibbings

(PS: really got to stop talking to myself!)
 
E

edam

Hi there,

Sorry I've been absent from this discussion for so long (it's been a
week now!). Some things came up. I'll try and answer a couple of
questions.

* edam:

Don't do that. It's one more character to type and non-standard. main must
return int.

Whoops. Thanks. ;o)

edam said:
Given this, I cannot understand why a *member function* of the derived
class or of a friend class can not downcast a protected base class
reference to a derived class reference specifically because we are
using protected inheritance. Surely, if the intention of protected and
private inheritance is to protect and make private the implementation
of a class, you shouldn't be hiding that implementation from the class
its self and its friends! [my emphasis]

Reading this, the key question appears to be specifically about the
runtime failure of attempting a dynamic_cast from base reference to
derived reference *in a member function of the derived class*

You are absolutely correct. And the excerpt of the standards you
quoted nicely summs up the issue - thanks. I should have included this
in the original post. As it says, you can dynamic_cast() from a base
class (pointer/reference) to a derived class (pointer/reference) only
if it is a *public* base (not a protected or private base).

I just don't understand why! Surely this should be allowed when it is
done in a member/friend function?

Tim.
 
Ö

Öö Tiib

You are absolutely correct. And the excerpt of the standards you
quoted nicely summs up the issue - thanks. I should have included this
in the original post. As it says, you can dynamic_cast() from a base
class (pointer/reference) to a derived class (pointer/reference) only
if it is a *public* base (not a protected or private base).

I just don't understand why! Surely this should be allowed when it is
done in a member/friend function?

This is because with non-public base class the classes do not have
proper generalization/specialization relation. Or otherwise ... why
you hide your general and most common interface? non-public base is
therefore usually used when the relation between classes is
aggregation/composition, hovever usage of base class-style interface
is more convenient than data member interface. It is most proper to
view such base classes as data members whose virtuals derived class
can easly overload and whose non-private interface it can conveniently
use as its own.

Casting from component to whole does not make sense logically. I think
that C++ authors have forbidden it to indicate that there is something
wrong with your design and the relations between your classes.
 
Ö

Öö Tiib

Doh. I did strip it wrongly, it should be:
This is because with non-public base class the classes do not have
proper generalization/specialization relation. Or otherwise ... why
you hide your general and most common interface? non-public base is
therefore usually used when the relation between classes is
aggregation/composition, hovever usage of base class-style interface
is more convenient than data member interface. It is most proper to
view such base classes as data members whose virtuals derived class
can easly overload and whose non-private interface it can conveniently
use as its own.

Casting from component to whole does not make sense logically. I think
that C++ authors have forbidden it to indicate that there is something
wrong with your design and the relations between your classes.
 
P

Paul Bibbings

Öö Tiib said:
Doh. I did strip it wrongly, it should be:

This doesn't sit comfortably with me as an `explanation'. I can
understand the points that you make about how non-public inheritence
is conventionally used and, again, with regard to how this use models
aggregation/composition. In itself, this is a design choice, idiomatic
even. What I'm not comfortable with is the thought of a language
incorporating limitations *merely* for the purposes of constraining
design. C++ is a multi-paradigm programming language and, as such, can
either be the wind in your sails or the stone in your shoe, depending on
how you use it. It doesn't seem to me to be a proper concern of the
language itself to forbid me from forming constructs and designs that
merely "do not make sense logically."

Furthermore, I think that this mode of thinking in this instance is
inconsistent, since the same reasoning is not applied in preventing the
inverse `design error' of "casting from [whole] to [component]."

Although I am not the OP, I am as interested in the answer to this
question. For myself, I am perhaps wanting to hear how the downcast
from protected Base in the context of a member function of Derived is
disallowed for some reason that is `technical', so to speak; that it
would, perhaps, break encapsulation at some point `to the left' (so to
speak) of what, in its immediate context, appears to be a harmless cast.

In my own thoughts at the present I don't think that this is about
constraining design. I think that the alternative would, somehow,
*break* something. I just can't forsee what that might be. I am
wondering if it has something to do with bringing the complications of
multiple inheritance in to play.

Regards

Paul Bibbings
 
Ö

Öö Tiib

This doesn't sit comfortably with me as an `explanation'.  I can
understand the points that you make about how non-public inheritence
is conventionally used and, again, with regard to how this use models
aggregation/composition.  In itself, this is a design choice, idiomatic
even.  What I'm not comfortable with is the thought of a language
incorporating limitations *merely* for the purposes of constraining
design.  C++ is a multi-paradigm programming language and, as such, can
either be the wind in your sails or the stone in your shoe, depending on
how you use it.  It doesn't seem to me to be a proper concern of the
language itself to forbid me from forming constructs and designs that
merely "do not make sense logically."

The whole set of public/protected/private access restrictions feels
like set of idiomatic design constraints. There are no real, physical
concerns why someone can not access private members. These are still
very important constraints to make the language better suitable for
object oriented programming. That is why i feel that most behavior
related to access restrictions is made especially for supporting
object-oriented conventions.
Furthermore, I think that this mode of thinking in this instance is
inconsistent, since the same reasoning is not applied in preventing the
inverse `design error' of "casting from [whole] to [component]."

You should somehow be able to extract the component from whole. Up-
casting is the only operation that makes sense for doing it there.
Although I am not the OP, I am as interested in the answer to this
question.  For myself, I am perhaps wanting to hear how the downcast
from protected Base in the context of a member function of Derived is
disallowed for some reason that is `technical', so to speak;  that it
would, perhaps, break encapsulation at some point `to the left' (so to
speak) of what, in its immediate context, appears to be a harmless cast.

Possibly there are better explanations. My opinion is just an opinion,
i can not immediately indicate any words of Stroustrup to back me up.
In my own thoughts at the present I don't think that this is about
constraining design.  I think that the alternative would, somehow,
*break* something.  I just can't forsee what that might be. I am
wondering if it has something to do with bringing the complications of
multiple inheritance in to play.

That would be interesting, since the order in what the base classes
and members are constructed and also the order in what they are
located in memory seems same for all compilers and it does not
seemingly depend on access restrictions at all. There may be some sort
of optimization that such constraint allows, but i can not imagine it.
That does not matter of course since i can not imagine lots of things
that are possible. :D
 
P

Paul Bibbings

Öö Tiib said:
The whole set of public/protected/private access restrictions feels
like set of idiomatic design constraints. There are no real, physical
concerns why someone can not access private members. These are still
very important constraints to make the language better suitable for
object oriented programming. That is why i feel that most behavior
related to access restrictions is made especially for supporting
object-oriented conventions.

I can understand what you're saying here and, of course, my sense of
`unease' is not firmly grounded in anything that I could call support
for it.
Furthermore, I think that this mode of thinking in this instance is
inconsistent, since the same reasoning is not applied in preventing the
inverse `design error' of "casting from [whole] to [component]."

You should somehow be able to extract the component from whole. Up-
casting is the only operation that makes sense for doing it there.

There is sense in this too.
Possibly there are better explanations. My opinion is just an opinion,
i can not immediately indicate any words of Stroustrup to back me up.

I wonder if this is one to take to comp.std.c++, or if the OP might wish
to do that.
That would be interesting, since the order in what the base classes
and members are constructed and also the order in what they are
located in memory seems same for all compilers and it does not
seemingly depend on access restrictions at all. There may be some sort
of optimization that such constraint allows, but i can not imagine it.
That does not matter of course since i can not imagine lots of things
that are possible. :D

Broadly, the kind of `scenario' I have in mind is something akin to the
example given (in a completely unrelated context) in [conv.qual]/4. To
quote the relevant text:

"[Note: if a program could assign a pointer of type T** to a pointer
of type const T** (that is, if line //1 below was allowed), a program
could inadvertently modify a const object (as it is done on line
//2). For example.

int main() {
const char c = 'c';
char* pc;
const char** pcc = &pc; //1: not allowed
*pcc = &c;
*pc = 'C'; //2: modifies a const object
}
--end note]"

Intuitively, for me at least, I found myself wondering what could be
wrong with assigning a pointer of type T** to a pointer of type const
T**. It was only from studying the example that it became clear *how*
it would break something else, "elsewhere," or "off to the left" as I
put it.

In short, I was wondering if there might not be a similar `loophole', if
you like, that would be opened in the case under question, were it
allowed to cast from protected Base in the context we are discussing.

In a very early post to this thread Alf made the suggestion that it
might permit accessing the protected base of an unrelated class in a
similar hierachy (IIRC). Though he retracted the details of the example
he presented, I am left wondering if there was not something in what he
was suggesting nevertheless - perhaps in a `diamond' hierarchy involving
virtual bases at some level, or something like that.

Regards

Paul Bibbings
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top