views:

96

answers:

4

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

+3  A: 

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.

Troubadour
May as well make Downcast generic. Should probably call it "static_downcast" also, to differentiate from the dynamic version you may very well end up having to write later.
Noah Roberts
@Noah Roberts: Good points.
Troubadour
That should be `resize()`, not `reserve()`. Either that, or use `back_inserter(bVec)` instead of `bVec.begin()` to push the objects instead of overwriting them.
Mike Seymour
@Mike Seymour: Woops, thanks. I already had `back_inserter` in my answer by the time you commented but I completely missed the `reserve`!
Troubadour
@Troubador: "to be absolutely sure the memory allocation is all done at once" - you can optionally call `reserve()` in the second case if you want that. In the first case, you must call `resize()`, since `transform` will expect valid objects to assign the results to.
Mike Seymour
@Mike Seymour: Good point about the memory allocation. I'd copied `reserve` straight from the original question without really paying attention and hence didn't realise it was not `resize`.
Troubadour
A: 

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.

Noah Roberts
+3  A: 

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>());
Mike Seymour
The OP should go with this one IMHO.
Noah Roberts
All caps identifiers are normally reserved for MACROs.
DeadMG
I'm not sure that this is any more elegant than the code of the OP, but it is the STL way :) I hate searching around to figure out what static_caster does.
@DeadMG: On the very rare occasions that I use macros, they don't pollute the rest of the code. But I'll change it anyway.
Mike Seymour
this is great thanks!
If you want to get pedantic about naming conventions, usual approach is to use camel case for template parameters with the concept they're supposed to follow, if any, in there somewhere. So they'd be "From" and "To" in this case...or maybe "Base" and "Derived".
Noah Roberts
@Noah: What "usual approach"? Unlike some other languages, C++ doesn't have a Central Commitee for Aeshthetic Fetishes, and the usual approach is to name things so they're readable and not sweat the small stuff. And "Base" and "Derived" would be wrong, because the two types don't have to have that relationship.
Mike Seymour
But I'll change it again anyway, because the syntax highlighting thinks "from" is a keyword, which looks weird.
Mike Seymour
@Mike - have a look at boost, the standard library, etc...
Noah Roberts
@Noah: Yes, most organisations have their style guides, and I've been paying lip service to such things all my working life. I'm sure many of them follow many of the same conventions that you do. If you're trying to convince me of the need for, or the existence of, a set of universal capitalisation rules, then you will fail. Write code as you see fit, as long as it's readable and acceptable to your peers, and leave the pedantic quibbling to others.
Mike Seymour
I think you just made my "prick of the week" award list. No reason to get so defensive over something so stupid. You went from all caps because someone said that's for macros to all lower, which is worse IMHO. I have the right to mention that and I did explicitly state it was a pedantic issue. So all you're doing now is being a total twunt.
Noah Roberts
@Noah: Read my response as defensive if you like; it was intended as a suggestion to save the pedantry for issues that matter, and not to sweat the small stuff. It's certainly your prerogative to comment if my style doesn't match your own, or to regard me as a twunt (great word, by the way) if I don't agree that your way is the One True Way. It's also my prerogative to respond when someone tries to argue that my arbitrary choices are wrong. Sometimes that prompts them to think about what is really worth arguing about; sometimes they just continue being a twunt in their own way.
Mike Seymour
+1  A: 

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;
}
John Dibling