0

I created a small sample which demonstrates the problem. I sure don't understand whats going wrong here. Using Visual Studio 2010.

The classes are (very loosely) modeled after MFC because this is what I need to use it for. Basically I wanted to create a panel class, which can contain other panels or controls so I added a component class which keeps track of the id and the parent, and the panel class which acts as a container for it's components.

Now what I don't understand is why I get this compiler error and why I get it only when I use a control but not with a subpanel.

#include <iostream>
#include <algorithm>
#include <vector>

class CWnd
{
public:
    CWnd(CWnd *pParent = NULL, int nId = -1)
    {
        mId = nId;
        mParent = pParent;
        mClassname = NULL;
    }

    void setId(int nId) { mId = nId; }
    int getId(void) const { return mId; }

    void setParent(CWnd *pParent) { mParent = pParent; }
    CWnd *getParent(void) const { return mParent; }

    bool create(const char *pClassname, int nId, CWnd *pParent)
    {
        mId = nId;
        mParent = pParent;
        mClassname = pClassname;

        return true;
    }

private:
    int mId;
    CWnd *mParent;
    const char *mClassname;
};

class Ctrl : public CWnd
{
public:
    Ctrl(CWnd *pParent, int nId = -1)
        : CWnd(pParent, nId)
    {
    }
};

class Dialog : public CWnd
{
public:
    Dialog(CWnd *pParent, int nId = -1)
        : CWnd(pParent, nId)
    {
    }

    bool create(int nId, CWnd *pParent)
    {
        CWnd::create("dialog", nId, pParent);

        return true;
    }
};

class View : public CWnd
{
public:
    View(CWnd *pParent = NULL)
        : CWnd(pParent)
    {
    }
};

template <typename W>
class Component : public W
{
public:
    Component(CWnd *pParent = NULL)
        : W(pParent)
    {
        mId = -1;
        mParent = pParent;
    }

    virtual bool create(CWnd *pParent = NULL)
    {
        if(pParent)
            mParent = pParent;

        W::setParent(mParent);
        W::setId(mId);

        return true;
    }

private:
    int mId;
    CWnd *mParent;
};

class Panel : public Component<Dialog>
{
public:
    Panel(CWnd *pParent = NULL)
        : Component(pParent)
    {
    }

    virtual bool create(CWnd *pParent = NULL)
    {
        if(pParent != NULL)
            setParent(pParent);

        Dialog::create(getId(), pParent);

        for(Components::iterator it = mComponents.begin(); it != mComponents.end(); ++it)
        {
            if(!(*it)->create(this))
                return false;
        }

        return true;
    }

    void addComponent(Component *pComponent)
    {
        if(std::find(mComponents.begin(), mComponents.end(), pComponent) == mComponents.end())
            mComponents.push_back(pComponent);
    }

    void removeComponent(Component *pComponent)
    {
        Components::iterator pos = std::find(mComponents.begin(), mComponents.end(), pComponent);
        if(pos != mComponents.end())
            mComponents.erase(pos);
    }

protected:
    typedef std::vector<Component *> Components;

private:
    Components mComponents;
};

int main()
{
    View v;
    Panel d;
    Panel p;
    Component<Ctrl> listbox;
    Component<Ctrl> tab;

    // these are generating the error
    d.addComponent(&listbox);
    d.addComponent(&tab);

    // only this one works.
    d.addComponent(&p);

    std::cout << "\nDone! Press any key..." << std::endl;
    std::cin.ignore();

    return 0;
}

The error message that I get is:

'Panel::addComponent' : cannot convert parameter 1 from 'Component<W> *' to 'Component<W> *'
Devolus
  • 21,661
  • 13
  • 66
  • 113

3 Answers3

3

There is no type Component, so there is no type Component*. However, in the context of the Panel class, since it is derived from Component<Dialog>, the name Component is actually injected, and refers to Component<Dialog>. So the proper signature of addComponent is:

void addComponent(Component<Dialog> *pComponent)

But Component<Ctrl> does not derive from Component<Dialog>, so a Component<Ctrl>* cannot be passed to this function.

What you might want to do is derive Component from a second base class (which is not derived from anything).

class Component_base
{
    // component common functionality
};

template<typename W>
class Component : public W, public Component_base
{
    ...
};

Then your Panel class can store:

std::vector<Component_base*> Components;
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • But I specifically did NOT specify the type in `addComponent` so shouldn't it take ANY component as it's argument? That was my idea (similar to a Java generic Component>), I would have thought that it should deduce the type from the parameter and not from the class its used in. That's normaly the way that arguments work, right? – Devolus Jun 05 '16 at 16:47
  • 1
    @Devolus: No. There is no such thing as a `Component*`, because `Component` is not a type. However, in the context of the `Panel` class, the name is injected and actually refers to `Component` (because `Panel` derives from `Component`. – Benjamin Lindley Jun 05 '16 at 16:50
  • If you want to have a common base class for all `Component<...>` classes, you must have `Component` derive from some non-template `Component` base class: `class Component { ... }; template class Component : public Component { ... };` – JohnB Jun 05 '16 at 16:52
  • At least I understand now the problem. I thought that Component refers to an unspecified type of Component where W can be anything. – Devolus Jun 05 '16 at 16:52
  • @JohnB, I know, but then I get the diamond problem, which I wanted to avoid :) – Devolus Jun 05 '16 at 16:53
  • Diamond problem? Where? – JohnB Jun 05 '16 at 16:53
  • @Devolus: No need for a diamond. The base class does not need to be derived from anything. – Benjamin Lindley Jun 05 '16 at 16:54
  • Because my conponent will be a BaseComponent as well as an MFC class. – Devolus Jun 05 '16 at 16:54
  • OK. I have to give that a try and see if that works as I intended it. – Devolus Jun 05 '16 at 16:55
  • @Devolus: You might want to have a look at this talk, I guess: https://www.youtube.com/watch?v=bIhUE5uUFOA It is about avoiding deep inheritance hierarchies when interfacing existing frameworks. – JohnB Jun 05 '16 at 17:00
1

There is something called injected-class-name. For class templates, this means that within the scope of the class template the class template name refers to the specific instantiation - not the template itself:

template <class T>
class C {
    C(C<T> const& ) = default; // one way to write the copy constructor
    C(C const& ) = default;    // exactly equivalent to the above
};

Inherited classes inherit all the injected class names as well. So when you write:

class Panel : public Component<Dialog>

we have multiple different injected names: Panel, Component, Dialog, and CWnd. It's the second one that's significant: within the scope of Panel, Component refers to the specific class Component<Dialog> and not the class template Component. Thus:

void addComponent(Component *pComponent)

is shorthand for (and exactly equivalent to):

void addComponent(Component<Dialog> *pComponent)

There is no class relationship between Component<Dialog> and Component<Ctrl>, hence the error.


Based on your comments, you actually wanted this to be a function template. So you have to explicitly make it one:

template <class W>
void addComponent(Component<W> *pComponent) { ... }

But this won't work since you're holding onto a vector<Component<Dialog>*>. You'll need to change that as well - perhaps to vector<CWnd*>.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks for the explanation. Templates always provide a new confusion for me, when I think that I finally understood it a little bit. :) Since I have to store it in an array, It boils down to creating a common base class. – Devolus Jun 05 '16 at 17:00
0

your problem is that there is no conversion from Component<Ctrl>* to Component<Dialog>*. d.addComponent(&p); works because d and p are the same type. You need to create common base class for Component<Ctrl>* and Component<Dialog>* to make such conversion or create converting constructor between those 2 types.

paweldac
  • 1,144
  • 6
  • 11