C++, std::list, assignment, inheritance
Asked Answered
B

5

7
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?

Brigandage answered 16/4, 2016 at 22:38 Comment(0)
M
6

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());
Monroemonroy answered 17/4, 2016 at 0:27 Comment(0)
P
4

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.

Phonetics answered 16/4, 2016 at 22:51 Comment(2)
What about just using std::list::assign(iter, iter) instead of the copy-inserter pattern?Monroemonroy
Excelllent ! I falsely assumed the range had to be a list of the same type (and in addition I love inserters). You should provide this answer, so that we can upvote ! .Phonetics
F
3

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.

Fingerboard answered 16/4, 2016 at 22:48 Comment(0)
R
2

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.

Reeher answered 16/4, 2016 at 22:52 Comment(3)
All of your points are valid. And that was my first thought when reading the question. But since the author did define an assignment operator for b=a, working with std::list<B> is acceptable, even by value.Pterosaur
@Pterosaur Right, but that doesn't really help him in assigning a std::list<B> to a std::list<A>. It would just mean that he could assign A instances into a std::list<B>. I assume his intent was to avoid anything for which he basically has to recreate the list to assign it.Reeher
True. But fortunately, the original question was about getting bb = aa to work -- i.e. populating std::list<B> from std::list<A>. And that's is a valid question in this specific case.Pterosaur
P
1

I don't think anyone actually tried to answer the question of how to do the assignment of bb = aa;

As previously mentioned, you cannot define an implicit cast or assignment operator for std::list<B> as a free operator. But you do have two options. Both of them rely on subclassing this collection... which is OK as long as you don't allow any additional state.

Option #1, is to define BB as subclass of std::list<B> and bb would use these classes. (BB would get an additional assignment operator.)

Option #2, is to use a helper class, and is likely closer to your original intent. The helper class would be defined like the BB class described above, adding an implicit conversion operator back to std::list<B>.

A complete example follows:

class A {};

class B: public A {
public:
    B() = default;
    B( const A &rhs ) {} // Empty because A and B are not defined with any state.
    B& operator= ( const A &rhs ) { return *this; }
};

class BB : public std::list<B> {
public:
    BB() = default;
    BB(const std::list<A> &aa) { assign( aa.begin(), aa.end() ); }
    BB& operator= ( const std::list<A> &rhs ) { assign(rhs.begin(), rhs.end()); return *this; }
    operator std::list<B>() { return *this; }
};
// No new member variables or virtual methods are allowed.
static_assert( sizeof (std::list<B>) == sizeof (BB), "Bad derivation of std::list<B>" );


A a;
B b;

b = a; // works

std::list<A> aa;
std::list<B> bb;

BB helper;
bb = helper = aa;  // Option #2; bb is std::list<B>

Or you could just use BB directly:

BB bb = aa;        // Option #1; use bb just like std::list<B>
Pterosaur answered 17/4, 2016 at 3:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.