views:

309

answers:

8

I'm trying to use templates to get std:list of items, where each item has a pointer to the list which contains it, but I keep hitting a compiler message.

Here's a very stripped down version of the code.

template <class E> class Item  
{
    public:
     E* owner; // pointer to list that owns us.
};

template <class E> class BaseList: public std::list<E>
{
protected:
    typedef std::list<E> inherited;
public:

    void push_back(const E &e)
    {
     E tmp(e);
     tmp.owner = this;  // This line gives the error.
     inherited::push_back(tmp);
    }
};

class MyList;

class MyItem : public Item<MyList>
{
};

class MyList : public BaseList<MyItem>
{
};

void foo()  // test code to instantiate template
{
    MyList l;
    MyItem m;
    l.push_back(m);
}

However, my compiler barfs at the line:-

     tmp.owner = this;

Error is:

[BCC32 Error] Unit7.cpp(30): E2034 Cannot convert 'BaseList<MyItem> * const' to 'MyList *'

It's like "this" has somehow become const, but I can't see why. Compiler is Codegear C++Builder 2009.

I admit I'm not 100% happy using templates, so I'm unsure if this is my problem or the compilers. The same code without template use compiles fine, but obviously that's not what I want, as I have several item/list classes that want to work this way.

Also, is there a better technique that would avoid having all the "owner" pointers in each item?

EDIT: I think I stripped the example down too far: "MyList" actually introduces new methods, which "MyItem" must then access through the "owner" pointer.

SUMMARY: Thanks for all comments and answers. As the accepted answer says, the problem is simply one of type incompatibility between pointer to a BaseList vs. MyList.

The issues raised about deriving from STL containers and alternative designs are also helpful, but the solution I've used is essentially identical to Luc Touraille's one below.

+8  A: 

At line 30, "this" is a pointer to a BaseList<MyIteM>, not a MyList. You can substitute a class with a derived one, but not the other way around.

You can either typedef MyList to be a BaseList<MyItem>, like so:

typedef BaseList<MyItem> MyList

or let MyItem derive from Item<BaseList<MyItem> > instead.

When you derive from a type, you create a different type. When you typedef, you create an alias for that type. So when you typedef the compiler will accept this.

Dave Van den Eynde
Thanks - See the clarification - Those approaches won't allow access to the MyList methods.
Roddy
Well, then you need to revise your code in other ways.
Dave Van den Eynde
To access MyList methods, you indeed need to parameterize MyItem with MyList. So the only problem is the affectation of `this` to `owner`, which can be resolved using a cast.
Luc Touraille
@Dave - Your answer pointed out the flaw in my code - the solution I've implemented is the same as Luc suggested. The cast is obviously not ideal, but is better than a total rewrite at this point.
Roddy
+3  A: 

Shouldn't it be tmp.owner = static_cast<MyList*>(this). The type of E is MyList in the MyItem hence E* will be MyList* . The type of this pointer will be BaseList*, hence compiler gives the error that you can not convert the base class pointer to the derived class pointer.

Naveen
Ah but there's a catch: since it's a template, it will not always be MyList. So now you have another problem that you won't find until runtime.
Dave Van den Eynde
I am doing a static_cast. So in case the conversion is not possible won't it be caught during compilation time?
Naveen
static_cast does no runtime checks, and since you're converting pointers, it does no compilation checks either.
Dave Van den Eynde
It does *some* compile-time checks in the case of pointers: it makes sure that either one is void* or that one is unambiguously derived from the other.
James Hopkin
Yes, it is my mistake, static_cast<> doesn't do the validation I was thinking of.
Naveen
You've got the right idea ; all you have to do is cast to the correct type instead of always casting to MyList. To do so, you need a way of accessing the container type of the item. This can be achieved via a simple typedef.
Luc Touraille
+4  A: 

In addition to the answers you already have, I would also point out that the standard library collection classes are not intended to be derived from, as they do not have virtual destructors, and none of their member functions is virtual.

anon
Even worse: you can not intercept when an element is removed from a list seen as a std::list. Do not inherit publicly.
Luc Hermitte
Nothing wrong with deriving from a class with no virtual destructor and no virtual member functions, if you know what you're doing.
Dave Van den Eynde
You may know what you are doing, but will the users of your code?
anon
although you can still implement your own function that hide the stl functions, and call the destructor directly from you're own class. As long as you avoid any downcasting to the stl container itself, there shouldn't be any real problem
Grant Peters
@Neil: the users of the my code will always find a way to blow their leg off.
Dave Van den Eynde
You shouldn't, in general, derive from std::list, but for different reasons.
Dave Van den Eynde
That doesn't mean you hand them a gun.
JohnMcG
Look at it this way: if I choose to derive from std::list (which I probably never will, but bear with me), I would do it in such a way that it would change nothing about how to use the class. And the users of that class should be able to do whatever they want with it just as much as they can with the base class. That also means they shouldn't derive from it, unless they know what they're doing.That class won't be anymore a handgun than the base class, std::list, already is.
Dave Van den Eynde
+2  A: 

It's hard to say if there's a better solution, when you don't say what it is you need. Why do each element need a pointer to the list they're stored in?

Anyway, bad things can happen when you inherit from standard containers. They don't have virtual destructors, so you have to be very careful.

A better solution might be to just provide a free function performing the push_back:

template <typename T>
void push_back(std::list<T>& list, const T& t) {
        T tmp(t);
        tmp.owner = this;
        list.push_back(tmp);
}

Apart from avoiding the nonvirtual destructor problem, it also solves your compiler error, because you now only have one type of list.

Of course, if we know why you need this owner pointer in the first place, better still solutions may exist.

Edit

In response to your edit and the comments, use composition, not inheritance:

struct House {
  std::string zipcode;
  std::list<Person> persons;

  void AddPerson(const Person& person) {
    Person tmp(person);
    tmp.owner = this; // The owner field should be a house, not the list of persons.
    persons.push_back(tmp);
  }
};

Although I'm not sold on the almost circular references you get when a House stores a list of Persons, and a Person has a pointer to the House it's in.

I'd prefer to decouple these classes as much as possible. If I want to send a letter to a person, I'd call SendLetter(Person, House). Send a letter to this person in that house.

jalf
Thanks. The requirement is access to data that's "global" to the list items. Analogy: Mylist represents a "house", with a "zipcode" and a list of "person" items. I want to have a person.sendLetter method, which needs access to the zipcode, and don't want to keep passing "house" as a parameter all over the place. Probably object composition rather than inheritance will do a better job for me.
Roddy
Did you mean to pass the list by reference?
James Hopkin
Actually, that still doesn't provide a solution - I think the question requires getting back to the owner of the list (the derived class, as it's coded), not just the list
James Hopkin
+1  A: 

On the side note, you should not extend any classes from std, they are not built for it.

Specifically they don't have virtual destructor so when you call delete on pointer to base class your derived class's destructor will never get called.

You can read more on it http://stackoverflow.com/questions/679520/advice-on-a-better-way-to-extend-c-stl-container-with-user-defined-methods

stefanB
+1  A: 

I like jalf's free function idea. I'd make it:

template <class X, class Y> // X must have a push_back(Y) member, Y must have an X* owner member
void push_back(X& container, Y value)
{
    value.owner = container;
    container.push_back(value);
}

This is agnostic over whether the X passed is

  • a container itself,
  • is derived from a container as in the original code
  • or contains a container and has a forwarding push_back function
James Hopkin
true, I only made it a List<T> for simplicity. I agree, making it a separate template type provides a lot more flexibility. +1 from here :)
jalf
+1  A: 

As it has already been pointed out, the affectation

tmp.owner = this;

fails because this doesn't have the same type as tmp.owner. One solution is to perform a cast, but to do so, you need to provide the container type to BaseList. This can be done using a typedef in Item. Here is the code:

template <class Item> class BaseList
{      
  public:

    void push_back(Item i)
    {
        i.owner = static_cast<Item::containerType *>(this); // note the cast
        items.push_back(i);
    }

    Item & back() { return items.back(); }

  protected:
    std::list<Item> items;
};


template <class Container> class Item
{
    public:
        typedef Container containerType; // Typedef used by BaseList

        containerType* owner; // pointer to list that owns us. 
};

I also removed the public derivation of std::list: as many said, this is (most of the time) best avoided ; you should consider using composition, or maybe private inheritance.

P.S.: I tried making owner private and BaseList<Item>::push_back friend of Item, but I didn't manage to do it. Is it possible at all? (If too long to answer in comment, feel free to ask a question and answer it)

Luc Touraille
Thanks - the typedef and cast is exactly the solution I'd come up with myself. It acheives exactly what I needed. Like you, I couldn't get friend/private working. Time for another question, maybe...
Roddy
+1  A: 

Regarding const: The type BaseList<MyItem> * const that the compiler mentions is a red herring -- it's not a pointer-to-a-const-object, but a pointer that is const, i.e. an address that won't change. (When you think about it, this never changes to point to something else, does it?)

j_random_hacker