f



Why can't pass vector of "Derived" to function that takes vector of "Base"?

I have these classes (elided methods):

class Base
{
public:
      Base(string name) {...}
};

class Derived : public Base
{
public:
      Derived(String name) : Base( name ) {...}
};

And neither of these work:

      /*** ATTEMPT ONE **/
      void create(std::vector<Base>& arr)
      {
            ...
      }

      int main()
      {
           std::vector<Derived> arr;
           create( arr );
      }

      /*** ATTEMPT TWO **/
      void create(std::vector<Base*>& arr)
      {
            ...
      }

      int main()
      {
           std::vector<Derived*> arr;
           create( arr );
      }

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Rob
4/28/2008 4:05:53 PM
comp.lang.c++.moderated 10738 articles. 1 followers. allnor (8509) is leader. Post Follow

8 Replies
1085 Views

Similar Articles

[PageSpeed] 0

On Apr 28, 6:05 pm, Rob <someidunknown1...@yahoo.com> wrote:
> I have these classes (elided methods):
>
> class Base
> {
> public:
>       Base(string name) {...}
>
> };
>
> class Derived : public Base
> {
> public:
>       Derived(String name) : Base( name ) {...}
>
> };
>
> And neither of these work:
> snip....

std::vector<Derived*>  and std::vector<Base*> are completely different
types, and unrelated. YOu may as well have said std::vector<Foo*>and
std::vector<Bar*> .
Something similar does work in Java (and that is a recent innovation
in itself -- for the longest time all collections in Java only held
Object and you had to cast things about to get it to work...), but not
C++.
The answer in C++ is to make create a template function, i.e.
template<class T>void create(T&v, int mytype){
   if(mytype==0)v.push_back(new Derived);
   else v.push_back(new Derived2);//something else  derived from Base

}
Lance


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Lance
4/28/2008 8:35:36 PM
On Apr 28, 6:05 pm, Rob <someidunknown1...@yahoo.com> wrote:
> I have these classes (elided methods):

> And neither of these work:
>
>       /*** ATTEMPT ONE **/
>       void create(std::vector<Base>& arr);
>
>       int main()
>       {
>            std::vector<Derived> arr;
>            create( arr );
>       }

A template instantiated twice, one holding type T1 and the other type
T2, are completely unrelated types according to the rules of the C++
type system.  As such, it's no different than trying to pass a Person
structure to a function expecting a Shape object.

Now logically, you might think that if the relationship between two
types T1 and T2 are polymorphic, then arrays or vectors of them ought
to be also, but it just isn't so.

Fist, be glad you used a vector and not a raw array (passed as a
pointer) as that *would* compile, and would have devastating results.
Consider the issue: How do you index into an array?  The way it works
is through pointer arithmetic on the size of the array element.

Since an array of N objects of type T live in consecutive memory, the
Ith element is calculated by multiplying the size of T by I, and
adding that offset to the base address of the array.

Now suppose each object of your base class (T1) was 10 bytes in size,
but each derived object (T2) was 24 bytes.  *If* you could use vectors
(or arrays) polymorphically, then element 0 would "line up" since they
both would have the same address, but when you try to access any
element after that, the arithmetic is wrong.  Suppose you index
element 3:

   Let ary_base be the address of element 0 in the array or vector.

   Then:

   calculated address: sizeof(T1)*3 + ary_base ==> 30 + ary_base

   desired address:    sizeof(T2)*3 + ary_base ==> 72 + ary_base

Since ary_base is the same in both cases, we can ignor it, and just
look at the offset to it.  We want 72, we get 30.  See the problem?

This is a well-known problem with arrays, and it extends to vectors
too, except that with vectors you have the benefit of a compile-time
detection of this subtle error, and with arrays you have runtime
failures.  There are many places you can get detailed discussion about
this issue, but the FAQ is a particularly good one:
http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.4

Depending on your situation, you can possibly work around this problem
it by changing your create() code into a function template, which
operates on a vector of the actual type you are using:

     template <typename T>
     void create(std::vector<T>& arr)
     {
     // ...
     }

-- 
Chris

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Chris
4/28/2008 8:36:06 PM
The problem I see is I don't know if C++ would know how to convert a
container<X> to a container<Y>. Even if C++ can switch between X and Y
without thinking, you're not working with X and Y, but container<X>
and <Y>.

But, as I said, C++ might not have a problem switching between X and
Y. What if you copy the contents of a std::vector<Derived*> to a
std::vector<Base*>? (And I DON'T mean std::vector<Base*> a = b, but
for the elements in b, assign to a.)

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Hakusa
4/28/2008 11:22:39 PM
On Apr 28, 6:05 pm, Rob <someidunknown1...@yahoo.com> wrote:
> I have these classes (elided methods):
>
> class Base
> {
> public:
>       Base(string name) {...}
>
> };
>
> class Derived : public Base
> {
> public:
>       Derived(String name) : Base( name ) {...}
>
> };
>
> And neither of these work:
>
>       /*** ATTEMPT ONE **/
>       void create(std::vector<Base>& arr)
>       {
>             ...
>       }
>
>       int main()
>       {
>            std::vector<Derived> arr;
>            create( arr );
>       }
>
>       /*** ATTEMPT TWO **/
>       void create(std::vector<Base*>& arr)
>       {
>             ...
>       }
>
>       int main()
>       {
>            std::vector<Derived*> arr;
>            create( arr );
>       }

vector<Base> and vector<Derived> are unrelated types.
They are not connected by inheritance like the types they
contain, so they don't behave polymorphically. Example:

class B {};
class D: public B {};

struct A { class B b; };
struct C { class D d; };

C c;
A& a( c ); // ERROR

You can insert Derived into vector<Base>, and Derived*
into vector<Base*> though (with slicing in the first case)

-- 
  Nikolai

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
nickf3
4/28/2008 11:22:53 PM
On Mon, 28 Apr 2008 16:05:53 CST, Rob wrote:
>       /*** ATTEMPT TWO **/
>       void create(std::vector<Base*>& arr)
>       {
>             ...
>       }
> 
>       int main()
>       {
>            std::vector<Derived*> arr;
>            create( arr );
>       }

It isn't type safe. Imagine if the langauge did allow you to do this. What
if create was implemented like:

void create(std::vector<Base*>& arr)
{
   arr.push_back(new Base());
}

Then since arr was passed by reference, the vector<Derived*> arr in main
would contain an object of type Base, when it's type says it only contains
objects of derived! Main could access an object of type base, and call a
Derived specific member function on it, and then you have undefined
behavior.

That would break type safty, and since the whole point of templates is type
safty, it would destroy the purpose of templates.

If you want to pass in a vector of Derived objects to a function that takes
Base objects, make a vector<Base*> and just populate it with Derived.
Derived objects are Base, but not the other way around.

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Brendan
4/29/2008 10:37:22 AM
Just for the sake of completeness, with regard to type equivalence of
templates, we may quote [temp.type]/1:

"Two template-ids refer to the same class or function if their
template names are identical, they refer to the same template, their
type template-arguments are the same type, their non-type template-
arguments of integral or enumeration type have identical values, their
non-type template-arguments of pointer or reference type refer to the
same external object or function, and their template template-
arguments refer to the same template."

Therefore, std::vector<Base>/std::vector<Base*> and
std::vector<Derived>/std::vector<Derived*> are indeed completely
different and unrelated types.

Regards,
David

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
David
4/29/2008 10:38:26 AM
On Apr 28, 5:05 pm, Rob <someidunknown1...@yahoo.com> wrote:
> I have these classes (elided methods):
>
> class Base
> {
> public:
>       Base(string name) {...}
>
> };
>
> class Derived : public Base
> {
> public:
>       Derived(String name) : Base( name ) {...}
>
> };
>
> And neither of these work:
>
>       /*** ATTEMPT ONE **/
>       void create(std::vector<Base>& arr)
>       {
>             ...
>       }
>
>       int main()
>       {
>            std::vector<Derived> arr;
>            create( arr );
>       }
>
>       /*** ATTEMPT TWO **/
>       void create(std::vector<Base*>& arr)
>       {
>             ...
>       }
>
>       int main()
>       {
>            std::vector<Derived*> arr;
>            create( arr );
>       }
>

Take a look at Bjarne Stroustrup's technical FAQ:

http://www.research.att.com/~bs/bs_faq2.html#conversion

- Anand


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Anand
4/29/2008 10:44:31 AM
> A template instantiated twice, one holding type T1 and the other type
> T2, are completely unrelated types according to the rules of the C++
> type system.  As such, it's no different than trying to pass a Person
> structure to a function expecting a Shape object.
>
> Now logically, you might think that if the relationship between two
> types T1 and T2 are polymorphic, then arrays or vectors of them ought
> to be also, but it just isn't so.

Thank you everyone for the responses. I understand now. It is
different in Java and I think that threw me off.

> Fist, be glad you used a vector and not a raw array (passed as a
> pointer) as that *would* compile, and would have devastating results.
> Consider the issue: How do you index into an array?  The way it works
> is through pointer arithmetic on the size of the array element.

Well, while I know this is how it is, two things:

1. They could have overriden the []operator so that it works with non-
contiguos data or with data of changing sizes.
2. A vector of bools is exactly that - it's not a normal container and
the data is retrieved through indirection so they have done something
like that before

It might have given a performance hit but I think it could be done.


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Rob
4/29/2008 11:13:21 AM
Reply: