views:

815

answers:

4

What I want to do is this:

class Ba { }
class Da : public Ba {}
class Db : public Ba {}

class Bb // abstract base class that must not be a template.
{
    void Process()
    {
        list<Ba*>::iterator pos;
        // I known the derived class will also derive
        // from list<Dx*> where Dx derives from Ba
        for(pos = this->begin(); pos < this->end(); pos++)
            something(*pos);
    }
}

template<T> class L : public Bb , list<T*> // T is always derived from Ba
{
}

But that's invalid. So what's the next best thing?

An alternately formulation has a global Process get a passed a Bb pointer where the type is not know til run-time:

void GlobalProcess(Bb* bb) // don't know what kind of Bb (which L) got passed in.
{
        list<Ba*>::iterator pos;
        // I known the derived class will also derive
        // from list<Dx*> where Dx derives from Ba
        for(pos = bb->begin(); pos < bb->end(); pos++)
            something(*pos);
}

I have a few reasons for doing this but about half of it is feeling out the edges of the C++ system.


The simplest form of the problem I can come up with is that I need to iterate over a list<D*> from code where all the info I'm allowed to use at compile time is that D derives from B. This situation can arise from being passed a list<D> via a pointer to an abstract base class or where the code its self is in a base class.

This would all work just fine if only list<D*>::iterator were derived from list<B*>::iterator

+5  A: 

First of all, don't inherit from std::list<>, the classes in the STL (with a few minor exceptions) are not meant to be derived from. They don't have virtual members, there is no benefit from doing so, it can only result in problems.

You question is a little vague and confusing. But assume you want to do this:

struct A {};
struct B : public A {};
std::list<B> l;

for(std::list<A>::iterator it = l.begin(); it != l.end(); ++it) {
    whatever(*it);
}

clearly this is invalid. But you can do something like this:

struct A {};
struct B : public A {};
std::list<B> l;


void whatever(A& a) {
    // do something with an A
}

std::list<B> l;
for(std::list<B>::iterator it = l.begin(); it != l.end(); ++it) {
    whatever(*it);
}

or better yet:

struct A {};
struct B : public A {};
std::list<B> l;

void whatever(A& a) {
    // do something with an A
}

std::for_each(l.begin(), l.end(), whatever);

EDIT:

To do what you want, you just need to do a little extra template work like this:

class Ba {};
class Da : public Ba {};
class Db : public Ba {};

template <class T>
class Bb {
    void Process() {
        list<T>::iterator pos;
        for(pos = begin(); pos < end(); pos++)
            something(*pos);
    }
}

template <class T>
class L : public Bb<T>, std::list<T> {
}

or you could make Process and interface like this:

template <class T>
class Bb {
public:
    virtual void Process() = 0;
}

template <class T>
class L : public Bb<T>, std::list<T> {
public:
    virtual void Process() {
        list<T>::iterator pos;
        for(pos = begin(); pos < end(); pos++)
            something(*pos);
    }
}

But it would be far better not to inherit from std::list<> as it will almost certainly lead to errors. Better to have std::list<T> be a member of the L class.

Evan Teran
Nope that doesn't work. The code that needs to do the iteration doesn't known the actual type of the list, just that it is a `list<>` of something derived from some given base class. The situation actual is as show where the iterating code is in a member function of an abstract base class where the concrete derived class must also inharets `list<Derived>`
BCS
Could you be more clear in your question then. As is, it is not very clear what you are trying to accomplish.
Evan Teran
Good point on inheritance vs. composition. (However I don't think that changes anything re my issue.) The upshot of what I'm looking for is that I don't want `Process` to be a template function or a in a template class. To be frank, I'm not sure this is even possible.
BCS
+1 for "First of all, don't inherit from std::list<>"
fmuecke
A: 

This should work by moving the processing code into the templated class:

class Bb
{
    virtual void Process() = 0;
}

template<T> class L : public Bb , list<T> // T is always derived from Ba
{
    virtual void Process()
    {
        list<T>::iterator pos;
        for(pos = begin(); pos < end(); pos++)
            something(*pos);
    }
}

This way you can specify the correct type of the iterator in Process() and still call it from a generic *Bb or &Bb.

Edit:

If you really don't want to use a virtual function and want to have all work done in class Bb, then Bb needs to know about the list<T> it should access. So it somehow needs to know about the type T and it won't be generic for different types T anymore.

If you have several different types T you will need to use polymorphism somewhere which will mean to use some virtual functions or templates.

sth
One of the things I'm trying to avoid is having the iterating function be a template function. Firstly, because I want to be sure that I only get a single copy of it. Second, to be sure that I only access the list items via the base class interface (no hijacking) and third, because in order to access it from a `Bb` instance, I would then need to make the function virtual and I want to skip that orverhead.
BCS
The overhead for a virtual function is basically one pointer indirection per call. That's very little overhead and usually not worth the additional complexity you get from optimizing it away. The loop inside the function will take much more time than the one pointer indirection for the virtual function call.
sth
And why do you want to make sure that you only get a single copy of the function? Are you **that** constrained on memory? A few copies of that function won't take much space...
sth
OK, now debunk point 1 and more importantly, point 2.
BCS
Why I care is irrelevant. I do.
BCS
You ask how you could do this better, and doing it better might include that you actually don't need some of the requirements you thought you needed. So it's a valid question to ask if you are really sure that all of your requirements are necessary.About explaining your reasons for your requirements: People might be able to give you better solutions if they understand what you actually want to do.
sth
The most striped down problem is that I want a non template function to iterate over a list of references (see comment to Ralph) as a base class but where the real type of the list uses a derived class. (I'll add another formulation of the same problem.)
BCS
A: 

If your iterating code is in the base class, then the list you are iterating over, must also be in the base class. This leads to the question, of what type of object the list should contain since you want to determine the type in the derived class. To solve this problem you can store shared pointers, effectively having a list, which can store polymorphic types. Here is the code:

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

class Ba { };
typedef boost::shared_ptr<Ba> BaSP;

class Da : public Ba {};
class Db : public Ba {};

void something(BaSP& a) {
    // do something with an Ba
}

class Bb // abstract base class that must not be a template.
{
protected:
    std::list<BaSP> list_;
    void Process()
    {
        std::list<BaSP>::iterator pos;
        for(pos = list_.begin(); pos != list_.end(); pos++)
             something(*pos);
    }
};

class L : public Bb 
{
    L() { 
        // the list can store Da and Db objects 
        boost::shared_ptr<Da> da;
        list_.push_back(da);
        boost::shared_ptr<Db> db;
        list_.push_back(db);
    }
};

This is the next best thing to the code you posted. If you give us the bigger picture of what you are trying to achieve, we might come up with a better design.

Ralph
Ah... I feel a bit sheepish... I forgot to make it a list of pointers (I'm coming to C from languages where objects are always references) I guess that would make things a bit clearer.
BCS
Better yet, make it a list of shared pointers. shared_ptr is part of the next standard, already available as technical report (tr1). If you're not using it yet, you can get the shared_ptr from boost.Using shared_ptr's will save you a lot of hassle and feel more natural, since you are coming from languages where objects are always references.
Ralph
+2  A: 

It is basically imposisble to get exactly what you want in C++. The language does not work this way, X : Y does not imply C<X> : C<Y> which is effectively the rule you want to apply. Why doesn't it? There are a few reasons:

  • C may not be a container at all (there is no way to know).
  • Template specialization (C<X> could have entirely different code than C<Y>).
  • You open yourself up to problems like (pseudo code because this problem isn't exclusive to C++)

    class Base
    {
       void method();
    };
    
    
    class Child : public Base
    {
       void newmethod();
    };
    
    
    list<Base> base_list;
    list<Child> child_list;
    
    
     void safe(list<Base> list)
     {
        for(it in list)
        { it->method(); }
     }
    
    
     void still_safe(list<Child> list)
     {
        for(it in list)
        { it->newmethod(); }
     } 
    
    
     safe(base_list);
     safe(child_list);
     still_safe(child_list);
    
    
     void unsafe(list<Base> list)
     {
        list.push_back(new Base);
     }
    
    
     unsafe(base_list); // safe
     safe(base_list);
     unsafe(child_list); // maybe we get away with this
     still_safe(child_list); // KA BOOM! because the last item in child_list can't newmethod()
    

Note that you can do a more limited form of this (in a way that works and is safe) in some languages, but I can't think of any that will allow specifically what you are asking for. See also http://en.wikipedia.org/wiki/Covariance%5Fand%5Fcontravariance%5F%28computer%5Fscience%29

The real solution to this, in C++, for this sort of problem, is to make use of polymorphism (whether through templates parametrized on the list/iterator type or virtual methods that operate on C and D).

Logan Capaldo