3
votes

When deriving a class from a template argument, the derived class doesn't have protected member access, even though it returns true from std::is_base_of

eg:

class A
{
    protected:
        virtual void Do() 
        {
            std::cout << "A::Do()";
        }
};

class B : public A
{
    protected:
        virtual void Do()
        {
            std::cout << "B::Do() - "; A::Do();
        }
};

and the template class

template <class BaseClass>
class C : public BaseClass
{
    public:
        C(BaseClass* c) :
            m_Base(c),
            m_Self(static_cast<C<BaseClass>*>(c))
        {}

        // this will never call the passed in class version
        void Do1()
        {
            BaseClass::Do();
        }

        // this has the error 'virtual int A::Do()' is protected within this context
        void Do2()
        {
            m_Base->Do();
        }

        void Do3()
        {
            m_Self->Do();
        }

        BaseClass* m_Base;
        C<BaseClass>* m_Self;
    };

if we then instantiate and call

int main()
{
    A a;
    B b;
    C<A> c(&b);

    std::is_base_of<A, C<A> >::value; // this is true

    c.Do1(); // calls A::Do()
    c.Do2(); // throws a protected access error
    c.Do3(); // calls B::Do()
}

casting the BaseClass pointer to a pointer of type C allows us to call the function correctly, both in that we can call the protected member function, and the override is correctly called.

this kind of thing doesn't happen with class B, which can call A::Do() directly, as it is derived from A.

C is derived from BaseClass, yet does not have access to the protected member functions. the pointer to BaseClass acts like it is an external pointer, not being called by the template class.

the overall question is therefore:

  • is this legal/safe/good code?
  • is there a reason why?
  • is there a better method to doing this?

edit for some more details and reasoning:

in this case, C is a wrapper class that allows me to forward on the function calls of an instance of B, whilst adding functionality to it. hence the need to store the instance of B, as its contains variables that will be updated, and B is the concrete instance that i store - or derive from and further specialise.

if we had

class D : public B
{
    protected:
        virtual void Do()
        {
            std::cout << "D::Do()";
        }
};

and we created as thus

D d;
C<B> c(&d)

i would expect when i call c.Do() that it calls the D specialisation, and on that particular instance.

however this goes, it does seem that i need to make C a friend of BaseClass as follows:

// forward declared as they live separately
template <class BaseType> class C;

class B
{
    ...

    friend class C<B>;
};
3
Pls add void at the virtual Do()Martin Morterol

3 Answers

3
votes

This is illegal with m_Base of type A*.

m_Base->Do();

You cannot access a protected member of an object except when the said object is *this. This is a huge simplification, but it works 99% of the time.

As a solution, you could make template<class T> class C a friend of A:

//define C beforehand

class A
{
friend template<class T> class C;
protected:
    virtual Do() 
    {
        std::cout << "A::Do()";
    }
};

Finaly, note that

m_Self(static_cast<C<BaseClass>*>(c))

is illegal: c being a BaseClass (in your usage, instancied from &b) you cannot cast it to a C<BaseClass>*.

3
votes

There is a confusion between derivation and composition: a derived class is a base class, a composed class has a base class as a member. Here your class is both derived and composed, c of class C<A> is A but also has a pointer to b of class B.

When you call c.Do1, you call the method Do of BaseClass, which is A in your example (because of C<A>), and you call A::Do.

When you call c.Do2, you call the method Do of b, which is protected!

When you call c.Do3, you call the method Do of m_self, which is a pointer to b, so you call B::Do.

I think you should just remove the 2 members m_Base and m_Self, and your constructor would be:

C: BaseClass() {}

Then you would have:

C<A> c;
c.Do();  // calls A::Do
C<B> d;
d.Do();  // calls B::Do

You can also override Do in C.

-1
votes

You don't need to store a pointer of BaseClass. C can't access m_base because Do() is protected, which means is private from the outside.

you can just call

BaseClass::Do()

because in that context is public (you're calling a method of your parent class).

Except for storing pointers, your approach is used for static polymorphism and policy based design.

You can find more in this post: C++: Inherit class from template parameter

You would like to take a look to the Curiously recurring template pattern