views:

318

answers:

4

Hi all,

I have a method

void foo(list<shared_ptr<Base>>& myList); 

Which I'm trying to call with a two different types of lists, one of DerivedClass1 and one of DerivedClass2

list<shared_ptr<DerivedClass1>> myList1; 
foo(myList1);
list<shared_ptr<DerivedClass2>> myList2; 
foo(myList2);

However this obviously generates a compiler error

error: a reference of type "std::list<boost::shared_ptr<Base>, std::allocator<boost::shared_ptr<Base>>> &" (not const-qualified) cannot be initialized with a value of type "std::list<boost::shared_ptr<DerivedClass1>, std::allocator<boost::shared_ptr<DerivedClass1>>>"

Is there any easy way to cast a container of shared_ptr? Of alternate containers that can accomplish this?

Update: Thanks to everyone who responded. Working within the confines of the language, it seems the best way to go while keeping the method 'as-is' is to use a container of shared_ptr and pass exactly that in (creating a new list at the call site).

I guess I pretty much already knew this, but I remembered reading about other parts of the boost library dealing with containers of shared_ptr and thought maybe it was solved more elegantly by someone else already. From my own further research however these seem to be geared more towards reducing overhead of shared_ptr in cases where a number of pointers are owned exclusively (therefore requiring a single lock per container rather than one per object in the container).

Thanks again, you guys are all awesome!

+1  A: 

Note that list<shared_ptr<Base>> and list<shared_ptr<DerivedClass1>> (or even shared_ptr<Base> and shared_ptr<DerivedClass1>) are completely different types, even if DerivedClass1 inherits from Base. So casting one to the other would in fact be wrong.

Consider the case when foo attempts to add new elements of type Base to myList. If you could cast myList1 into a list<shared_ptr<Base>> (e.g. with an old-style C cast), and passed it to foo, the result would not be nice.

So the only proper solution I see is to create a new list<shared_ptr<Base>> object and copy the contents of the other list into it, then pass that to foo, as is demonstrated by @James McNellis.

Péter Török
but still... if the method foo took a single element then you would be able to call foo(static_pointer_cast<Base>(objectOfTypeDerivedClass1))
Jamie Cook
@Jamie Cook, yes but that's a different question (I clarified my post a bit). Btw is static_pointer_cast a nonstandard VC++ extension?
Péter Török
@Jamie: Yes, there are special rules between `shared_ptr<Base>` and `shared_ptr<Derived>` because `shared_ptr` emulates a pointer. However, different containers are totally distinct. If you want the same function to work for containers of different types, use templates (compile-time polymorphism), or store pointers to base and use runtime polymorphism.
UncleBens
The `shared_ptr<>` is confusing the issue. You also cannot cast `list<Base*>` to `list<Derived*>`. They are unrelated types, as far as the compiler is concerned.
KeithB
@Péter Török, yeah I guess it is a different question... perhaps I should have clarified that the argument list would be const. But I don't think that changes anything meaningful, the compiler still won't be able to do any easy conversion. boost::static_pointer_cast is from boost::shared_ptr and basically does what it says (there is also boost::dynamic_pointer_cast)
Jamie Cook
+1  A: 

You can't cast a container of one type to a container of another type. There are a few ways to create a new container from an existing container, if the type of object stored by the existing container is convertible to the type of object stored by the new container:

You can use std::copy to do the conversion element-by-element:

list<shared_ptr<Base> > baseList;
list<shared_ptr<Derived> > derivedList;
std::copy(derivedList.begin(), derivedList.end(), std::back_inserter(baseList));

You can also directly construct baseList using the begin and end iterators from derivedList:

list<shared_ptr<Base> > baseList(derivedList.begin(), derivedList.end());

If your function can take a const reference instead, you can construct a temporary in the function call:

typedef list<shared_ptr<Base> > BaseList;
foo(BaseList(derivedList.begin(), derivedList.end()));
James McNellis
Why would you use `copy` when `list` has a range-based constructor?
rlbond
As demonstrated in the second example?
James McNellis
+6  A: 
sbi
but isn't it weird that you should have to use a template function for what is well handled by basic inheritance in other cases?
Jamie Cook
@Jamie Cook: Nope. That is never handled by basic inheritance in any case. A container of Derived is not a container of Base. If that was allowed then you would be able to add elements of type Base to that container inside the function, breaking the type system.
David Rodríguez - dribeas
@Jamie: `T<A>` and `T<B>` are completely different classes for any template `T` and any different `A` and `B`, no matter how A and B are related.
Potatoswatter
@Potatoswatter: Not true in general, ironically shared_ptr itself is one such example. Generally speaking some template classes choose to allow such conversions via special templated copy constructors.
Terry Mahaffey
Jamie Cook
@Terry: The copy constructor allows one kind of `shared_ptr` to be converted to another, but they remain different types. It is possible (but confusing) to construct an inheritance hierarchy within the specializations of a single template, technically. Anyway, containers rely on range insertion for conversion-initialization, as in this answer.
Potatoswatter
@Jamie: Iterators are the minimal interface for… well, iteration. A container allows you to use one argument rather than two, but you still have to call `begin()` and `end()` on it inside anyway. A function in terms of iterators works with any container, as well as ranges within a container, C-style arrays, I/O streams, generator functions, etc.
Potatoswatter
@Potatoswatter - point taken. I've taken to using BOOST_FOREACH so my calling of begin/end is somewhat hidden from me most of the time.
Jamie Cook
A: 

The classical explanation why that wouldn't be a good idea goes as follows:

If you have a list<Apple*> nicely filled with Apples, and you were able to pass it to a function accepting a list<Fruit*>, then there is no stopping the function from putting new Oranges in the list. A list<Fruit*> is simply not substitutable for a list<Apple*> and there exists no relationship.

If you want a function that could work both with a list<Apple*> and list<Orange*>, you could use templates (whether with Iterators, or taking the Container itself, or taking a list<T*> - entirely your choice.


If you really badly wanted to pass a list<Apple*> as if a list<Fruit*>, then I suppose it would be technically possible to come up with proxies for the container / iterators, that use type erasure and what-not to cast the pointers internally / stop invalid casts (putting Oranges into list of Apples), so that you could make the function instead take a list_proxy<Fruit*> which accepts lists of derived types.

Something along the lines of (just a beginning):

#include <list>
#include <boost/shared_ptr.hpp>

template <class T>
class ListWrapperBaseAux
{
public:
    virtual ~ListWrapperBaseAux() {}
    virtual T* front() = 0;
};

template <class T, class U>
class ListWrapperAux: public ListWrapperBaseAux<T>
{
    std::list<U*>* list_ref;
public:
    ListWrapperAux(std::list<U*>* list_ref): list_ref(list_ref) {}
    virtual T* front() { return dynamic_cast<T*>(list_ref->front()); }
};

template <class T>
class ListProxy
{
    boost::shared_ptr<ListWrapperBaseAux<T> > list_ref;
public:
    template <class U>
    ListProxy(std::list<U*>& li): list_ref(new ListWrapperAux<T, U>(&li)) {}
    T* front() { return list_ref->front(); }
};

struct Fruit
{
    virtual ~Fruit() {}
};

struct Apple: Fruit {};
struct Orange: Fruit {};

void bake(ListProxy<Fruit> li)
{
    Fruit* f = li.front();
}

int main()
{
    std::list<Apple*> apples;
    bake(apples);

    std::list<Orange*> oranges;
    bake(oranges);
}
UncleBens