Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++, std::list, assignment, inheritance

class A, B;
class A {
    public:
        A& operator= ( const A &rhs ) { return *this; }
};
class B: public A {
    public:
        B& operator= ( const A &rhs ) { return *this; }
};
A a;
B b;
std::list < A > aa;
std::list < B > bb;
a = b; // works
b = a; // works
// aa = bb; // fails
// bb = aa; // fails

How do I get bb = aa to work?

like image 467
Y. L. Avatar asked Apr 16 '16 22:04

Y. L.


4 Answers

What you're missing here is that even though A and B are related types, std::list<A> and std::list<B> are actually unrelated types (other than both saying list). As such you can't use copy assignment to assign between them.

However, assuming that you're ok assigning the types A and B to each other using their assignment operators, you can use list's assign method to copy the range of iterators:

aa.assign(bb.begin(), bb.end());
bb.assign(aa.begin(), aa.end());
like image 198
Mark B Avatar answered Oct 31 '22 06:10

Mark B


You can do this with a copy and a back_inserter:

std::copy(aa.begin(), aa.end(), back_inserter<list<B>>(bb));

but under the condition that the target element (here B) can be constructed from the source element (here A). So here, you'd need a B constructor based on an A object.

Online demo

Note: If you don't have the constructor needed for the copy in the target class, but you have some other way to build a target object from the source, you can consider using std::transform()

By the way, attention: a=b works, but might result in slicing.

like image 24
Christophe Avatar answered Oct 31 '22 07:10

Christophe


That you can assign a object of type A to an object of type B does not mean that holds for list<A> and list<B> as well.

Instead of you can use std::copy:

std::copy(bb.begin(), bb.end(), std::back_inserter(aa)); // instead of aa = bb

EDIT: in order to use that you either have to call aa.resize(bb.size()) first, or better use a back_inserter as noted by @Christophe.


You should be clear about what you're doing here: it's either slicing (A = B) or assigning a non-final class (B = A). In order to avoid that, you should work with pointers and use the clone pattern.

like image 3
davidhigh Avatar answered Oct 31 '22 06:10

davidhigh


As far as the compiler is concerned std::list<A> and std::list<B> are disjoint types. This makes sense if you consider that std::list has internally allocated memory for A objects. Trying to assign B objects into that space could be disasterous.

Imagine, for example, that B has an extra property. Now, if you're trying to store a B into memory large enough for an A, it will likely not fit. Similar logic can be used to see why the other direction also fails: if you store an A into space for a B, the compiler expects that the extra property of the B it believes to be at that space is valid. If you assigned an A though, that space has who-knows-what in it.

If you want this assignment to be able to work, you're going to need to use some form of indirection. For example, you could use two std::list<A*> instances or two std::list<std::shared_ptr<A>> instances. This then works because a B* can be safely treated as an A*, at least assuming that the classes are properly written.

like image 2
Corbin Avatar answered Oct 31 '22 06:10

Corbin