views:

2207

answers:

5

My C++ framework has Buttons. A Button derives from Control. So a function accepting a Control can take a Button as its argument. So far so good.

I also have List<T>. However, List<Button> doesn't derive from List<Control>, which means a function accepting a list of Controls can't take a list of Buttons as its argument. This is unfortunate.

Maybe this is a stupid question, but I don't see how can I solve this :( List<Button> should derive from List<Control>, but I don't see a way to make this happen "automatically".

+6  A: 

How about using pointers? Just have a list of list<Control*> and put whatever Control-derived objects you like into it.

MrZebra
If you do decide to use a list of pointers, you will have to remember to delete the objects when you are finished with them (you loose the automatic memory management). To get it back, see Boost's ptr_container library. It has a container that manages a list of pointers as if it was a standard list.
Tom Leys
+2  A: 

Instead of using List<Button>, use List<Control*>, which are pointing to Buttons.This way, your function only has to take one type: List<Control*>.

Marcin
Sorry for not being clear. I do use pointers (smart pointers, actually). The function does take a List of Control*. The thing is I want to pass it a list of Button*.
ggambett
+6  A: 

Stroustrup has an item on this in his FAQ:

Why can't I assign a vector<Apple*> to a vector<Fruit*>

You can solve it in two ways:

  • Make the List contain pointers to Control . Then accept List<Control*>
  • Make your function a template. You can still use List<Button> and List<Control> then, but it's more boilerplate code, and not necassary most of the time.

Here is code for the second alternative. The first alternative is already explained by other answers:

class MyWindow {
    template<typename T>
    void doSomething(List<T> & l) {
        // do something with the list...
        if(boost::is_same<Control, T>::value) {
            // special casing Control

        } else if(boost::is_same<Button, T>::value) {
            // special casing Button

        }

    }
};

To restrict doSomething only for List<derived from Control>, some more code is needed (look for enable_if if you want to know).

Note that this kind of code (looking what type you have) is rather to avoid. You should handle such things with virtual functions. Add a function doSomething to Control, and override it in Button.

Johannes Schaub - litb
+5  A: 

I hate to tell you but if you're using a list of instances to Control instead of pointers to Control, your buttons will be garbage anyway (Google "object slicing"). If they're lists of pointers, then either make the list<button*> into list<control*> as others have suggested, or do a copy to a new list<control*> from the list<button*> and pass that into the function instead. Or rewrite the function as a template.

So if you previously had a function called doSomething that took a list of controls as an argument, you'd rewrite it as:

template <class TControl>
void doSomething( const std::list<TControl*>& myControls ) {
  ... whatever the function is currently doing ...
}

void doSomethingElse() {
   std::list<Button*> buttons;
   std::list<Control*> controls;
   doSomething( buttons );
   doSomething( controls );
}
Michel
Hmm, this may work. It looks somewhat ugly, though :)
ggambett
It looks ugly, but it's the heart of the standard template library and modern C++ programming. Check out the source code to std::list itself sometime. Not pretty.
Michel
My major issue with this is that (IIUC) it is entirely legal for the compiler to generate bit for bit identical functions for each and every type that is used. Oh well.
BCS
A: 

Generally, the C++ way to write algorithms that perform on a list, sequence, ... is to provide iterators as arguments.

template < class iterator >
doSomething(iterator beg, iterator end);

This solves the List< Button* > is not derived from List< Control* >. (using a template List< T* > would too, but that's kind of making your function generic-but-not-really)

In my experience, making good templatized functions operating on iterators can be a lot of (too much...) work, but it's the "C++ way"...

If you go this route, consider using Boost.ConceptCheck. It'll make your life a lot easier.

Pieter