moka
moka

Reputation: 4491

c++ std::copy with type cast to derived class possible?

I am pretty sure that there is no way around doing this explicitly but I would like to ask nontheless in case there is a better way. I have a base class A and a derived class B, now I have a std::list of A* which point to B*'s and I want to copy this list of A*'s to a std::vector of B*'s so basically I want to do this:

std::list<A*> aList = someObject.getAs();
std::vector<B*> bVec = std::vector<B*>(aList.begin(), aList.end());

I am pretty sure this should compile when the list and the vector would be the same type (eg both were A*'s), but since in this case A* is the base class of B* I can't do it this way, because I would have to explicitly typecast for instance like this:

std::list<A*> aList = someObject.getAs();
std::vector<B*> bVec;
bVec.reserve(aList.size());
std::list<A*>::iterator it = aList.begin();
for(it; it!=aList.end(); ++it)
{
   B* b = static_cast<B*>(*it);
   bVec.push_back(b);
}

Is there any more elegant way than my second approach or will I have to do it like that?

Upvotes: 13

Views: 7545

Answers (4)

Edward Strange
Edward Strange

Reputation: 40859

You could go the iterator adapter approach, but I'd suggest doing it correctly if you do. Either you need to override all the stuff that makes an iterator an "Iterator", or use Boost.Iterator, a library meant to make such things easier.

The other approach you would use is make a functor and use std::transform instead of std::copy. This would seem to me a much easier approach. If you're using a C++0x compiler you could even just use a lambda.

Edit: The person who suggested using an adapter pulled his answer so the first paragraph might not make a lot of sense. It used a wrapper around the vector iterators that returned B* instead of A* but it left out a whole lot of work that would be necessary to do it correctly.

Upvotes: 1

Mike Seymour
Mike Seymour

Reputation: 254461

It's not safe do the conversion implicitly, so you have to make it explicit. The standard algorithm for applying some kind of transformation to a sequence is std::transform, which you can use to populate an empty container as follows:

struct A {};
struct B : A {};

template <typename From, typename To>
struct static_caster
{
    To* operator()(From* p) {return static_cast<To*>(p);}
};

std::list<A*> a;
std::vector<B*> b;
std::transform(a.begin(), a.end(), std::back_inserter(b), static_caster<A,B>());

Upvotes: 20

Troubadour
Troubadour

Reputation: 13421

Define a functor to do the casting eg.

struct Downcast
{
    B* operator() ( A* a ) const
    {
        return static_cast< B* >( a );
    }
};

and then use std::transform instead of std::copy i.e.

bVec.resize(aList.size());
std::transform( aList.begin(), aList.end(), bVec.begin(), Downcast() );

Note you can also do

std::vector<B*> bVec;
std::transform( aList.begin(), aList.end(), std::back_inserter( bVec ), Downcast() );

in which case bVec will grow as needed but I prefer the first approach to be absolutely sure the memory allocation is all done at once. As @Mike Seymour points out though you could call bVec.reserve( aList.size() ) in the second case to ensure one allocation.

Upvotes: 7

John Dibling
John Dibling

Reputation: 101456

Use a transformation:

#include <cstdlib>
#include <vector>
#include <algorithm>
using namespace std;

class A
{
};
class B : public A
{
};

A* get_a() { return new B; }

B* make_b(A* a) { return static_cast<B*>(a); }

int main()
{
    vector<A*> a_list;
    vector<B*> b_list;

    generate_n(back_inserter(a_list), 10, get_a);
    transform(a_list.begin(), a_list.end(), back_inserter(b_list), make_b);

    return 0;
}

Upvotes: 2

Related Questions