Casting a shared_ptr container

I have a method

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

      

What I am trying to call with two different types of lists, one from DerivedClass1 and one from 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 an easy way to throw the shared_ptr container? From alternative containers that can do this?

Update . Thanks to all who responded. Working within the language, it seems like the best way to go while keeping the method "as is" is to use the shared_ptr container and pass it exactly (creating a new list at the invocation site).

I think I already knew a lot about this, but I remembered reading about other parts of the boost library dedicated to shared_ptr containers, and thought it might have been more elegantly put forward by someone else. However, from my own further research, they are more focused on reducing the shared_ptr overhead in cases where the number of pointers is exclusively owned (therefore, each container in a container requires one lock per container, not one per object).

Thanks again, you guys are awesome!

+2


a source to share


4 answers


You cannot throw one container type into another container type. There are several ways to create a new container from an existing container, if the type of the object stored in the existing container can be converted to the type of the object stored in the new container:

You can use std::copy

to perform elementwise transformation:

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

      

You can also build directly baseList

using begin and end iterators from derivedList

:



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

      

If your function can accept a const reference, you can create a temporary expression in the function call:

typedef list<shared_ptr<Base> > BaseList;
foo(BaseList(derivedList.begin(), derivedList.end()));

      

+3


a source


Modify foo

to conform to STL conventions and accept iterators:

template< typename It >
void foo(It begin, It end); 

      

Then pass iterators to your lists



list<shared_ptr<DerivedClass1>> myList1;
foo(myList1.begin(), myList1.end());
list<shared_ptr<DerivedClass2>> myList2;
foo(myList2.begin(), myList2.end());

      

And everything should work fine.

Edit :
Please note that this is not something special for smart pointers. A std::list<T1*>&

cannot be initialized with std::list<T2*>

, even if it T2

comes from T1

.

+7


a source


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

inherited from Base

. So casting one onto the other would actually be wrong.

Consider the case when foo

trying to add new type elements Base

to myList

. If you could overlay myList1

on list<shared_ptr<Base>>

(for example, the old-style C listing) and pass it on foo

, the result would not be pleasant.

So, the only correct solution I see is to create a new object list<shared_ptr<Base>>

and copy the contents of the other list into it, then pass that to foo

, as @James McNellis showed.

+1


a source


The classic explanation for why this would not be a good idea is as follows:

If you have a list<Apple*>

beautifully filled with Apples, and you were able to pass it to the receiving function list<Fruit*>

, then the function does not stop placing new oranges in the list. A list<Fruit*>

simply cannot be replaced for a, list<Apple*>

and there is no relationship.

If you want to use a function that can work with list<Apple*>

or list<Orange*>

, you can use templates (whether with iterators, or with the container itself, or with list<T*>

) - it's entirely your choice.


If you really really wanted to pass list<Apple*>

as if list<Fruit*>

, then I guess it would be technically possible to come up with proxies for the container / iterator that use type erasure, and what not to use pointers inside / stop invalid pushes (putting oranges in the apple list) so you can make a function instead list_proxy<Fruit*>

that accepts lists of derived types.

Something line by line (start only):

#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);
}

      

0


a source







All Articles