Inheritance and STL containers

M

moleskyca1

I've ran into a problem with inheritance and STL containers. The code
is too much to list, but here is the basic problem:

#include <queue>

using namespace std;

class Base
{
public:
typedef queue<Base *> BaseQ;

void DoBase(BaseQ &) {;} // should work on any queue of Base or a Base
subtype
};
class Derived: public Base
{
public:
typedef queue<Derived *> DerivedQ;
};

// have other derived types from base: Derived1, Derived2, etc...

int main(int argc, _TCHAR* argv[])
{
Derived *dp = new Derived();
Base *bp(dp); // (A) this is legal as expected (upcasting)

Derived::DerivedQ dv; dv.push(dp);

bp->loopBase(dv); // (B) compiler says this is illegal, cannot convert
from queue<Derived *> & to queue<Base *> &


return 0;
}

If line (A) is legal, why isn't like (B) legal as well? There are
operations in Base that must apply to containers of any derived of
base. It seems wasteful to have to create a temporary queue<Base *> and
load it with the contents of queue<Derived *> to call loopBase()
(that's what I do now). Is there a better way?
 
V

Victor Bazarov

I've ran into a problem with inheritance and STL containers. The code
is too much to list, but here is the basic problem:

#include <queue>

using namespace std;

class Base
{
public:
typedef queue<Base *> BaseQ;

void DoBase(BaseQ &) {;} // should work on any queue of Base or a Base
subtype
};
class Derived: public Base
{
public:
typedef queue<Derived *> DerivedQ;
};

// have other derived types from base: Derived1, Derived2, etc...

int main(int argc, _TCHAR* argv[])
{
Derived *dp = new Derived();
Base *bp(dp); // (A) this is legal as expected (upcasting)

Derived::DerivedQ dv; dv.push(dp);

bp->loopBase(dv); // (B) compiler says this is illegal, cannot convert
from queue<Derived *> & to queue<Base *> &


return 0;
}

If line (A) is legal, why isn't like (B) legal as well?

sometemplate<T> and sometemplate<TT> are *unrelated* types no matter
what relationship exists between T and TT. Unless they are the same,
any template instantiated from two different types produces two different
types as the result. No conversion exists between two different types
unless explicitly programmed (or part of the language, like int -> double).
There are
operations in Base that must apply to containers of any derived of
base. It seems wasteful to have to create a temporary queue<Base *> and
load it with the contents of queue<Derived *> to call loopBase()
(that's what I do now). Is there a better way?

Just use 'queue<Base*>' and make sure you apply polymorphism there.

V
 
M

moleskyca1

Ah, I actually fuond a better solution: convert DoBase() to a template
function. Then it wil work:

#include <queue>

using namespace std;

class Base
{
public:
typedef queue<Base *> BaseQ;

template <typename T>
void DoBase(queue<T> &) {;} // should work on any queue of Base or a
Base subtype
};
class Derived: public Base
{
public:
typedef queue<Derived *> DerivedQ;
};

// have other derived types from base: Derived1, Derived2, etc...

int main(int argc, _TCHAR* argv[])
{
Derived *dp = new Derived();
Base *bp(dp); // (A) this is legal as expected (upcasting)

Derived::DerivedQ dv; dv.push(dp);

bp->DoBase<Derived *>(dv); // (B) compiler says this is illegal,
cannot convert from queue<Derived *> & to queue<Base *> &



return 0;
}
 
M

moleskyca1

Victor said:
sometemplate<T> and sometemplate<TT> are *unrelated* types no matter
what relationship exists between T and TT. Unless they are the same,
any template instantiated from two different types produces two different
types as the result. No conversion exists between two different types
unless explicitly programmed (or part of the language, like int -> double).
Ok, this is what I try to understand. Why? There may be a good reason,
but I don't know. In by example,
Base *bp;
Derived *dp;
bp = dp; // this is legal and useful

why the compiler not use this convertion? Just trying to understand!
 
M

Mark P

Ok, this is what I try to understand. Why? There may be a good reason,
but I don't know. In by example,
Base *bp;
Derived *dp;
bp = dp; // this is legal and useful

why the compiler not use this convertion? Just trying to understand!

Because they're separate types and there is no inheritance relationship
between them. Just imagine if there were an explicit specialization of
sometemplate<Derived>. Then it might look completely different than
sometemplate<Base>.
 
V

Victor Bazarov

Ok, this is what I try to understand. Why?

I am not sure I understand the question... Why should they be related?
How would conversion work? Do you expect the conversion *implicit*? You
can program your own template containers which might provide conversions
from one to the other. My guess for the absence of the implicit conversion
is due to the inability to invent proper semantics for all cases.
There may be a good reason,
but I don't know. In by example,
Base *bp;
Derived *dp;
bp = dp; // this is legal and useful

why the compiler not use this convertion? Just trying to understand!

And do what? Would you expect the compiler to automatically convert
queue<Derived*> to queue<Base*> or queue<Derived*> to queue<Base*>*
or queue<Derived>* to queue<Base>*? What for? Treat all elements of
the container of Derived as if they were Base? You can do it now. Or
something else? What would be the use?

V
 
M

moleskyca1

Victor said:
And do what? Would you expect the compiler to automatically convert
queue<Derived*> to queue<Base*> or queue<Derived*> to queue<Base*>*
The conversion Derived * to Base * is available implicitly; my
queue said:
or queue<Derived>* to queue<Base>*? What for? Treat all elements of

What for? For one, I could have a function in Base
Base::DoBase(queue<Base *> &) operating on each Base *, but
polymorphism will let each derivation of Base in this queue morph the
behaviour as desired by that derivation. If another part of program has
a queue<Derived *>, but wants to call Base::DoBase with that queue as
the parameter, why not use the Derived * to Base * conversion that is
available?

Ok, 2nd part of my question is what is best solution. I posted a
solution (my last post of 7/27). If DoBase will invoke polymorphic
functions via Base *, then I could change the function declaration to:

template <typename T>
void DoBase(queue<T> &) {;}

How about this solution?

ps: Sorry my original post had a typo, the call to function loopBase is
not valid; it should call DoBase.
 
V

Victor Bazarov

[..] 2nd part of my question is what is best solution. I posted a
solution (my last post of 7/27). If DoBase will invoke polymorphic
functions via Base *, then I could change the function declaration to:

template <typename T>
void DoBase(queue<T> &) {;}

How about this solution?

This solution is not exactly the same. If you pass an object of type
'queue<Derived*>' to 'DoBase', 'T' will be deduced as 'Derived*', and
a new instantiation of 'DoBase' will be created. Any access to the
members of '*T' (Derived) will be made statically:

template <typename T>
void DoBase(queue<T> & q) {
/* get the iterator */
(*it)->somemember(); // here '*it' is of type 'Derived*'
}

(although it may not be as bad as it sounds).

What you might want to do is this:

void DoSingleBase(Base* pb) { /* something */ }

template <typename T>
void DoBase(queue<T> & q) {
/* get the iterator */
DoSingleBase(*it);
}

In this case the pointer that (*it) yields will be _converted_ to
to Base* and polymorphic behaviour will be ensured.

Or maybe I'm wrong. Try both and see.

V
 
N

Noah Roberts

The conversion Derived * to Base * is available implicitly; my
queue<Base *>. I think it's reasonable to expect queue<Base *> to be
assigned from a queue<Derived *>

Maybe you think it is reasonable but it just doesn't work. The two
resultant classes are different and unrelated. It is something you'll
need to learn about templates. You can't copy/assign a queue<int> to a
queue<long> either even though there is an implicit conversion. The
queue template would need copy/assignment operators that are templates
taking queue<T> as parameters; it doesn't and neither do any other std
containers that I am aware of.

I'm sure there are reasons why these operators where ommited from the
standard but I don't off hand know them. It is what it is.
 

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

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top