3

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>;
};
CommanderBubble
  • 333
  • 2
  • 9

3 Answers3

3

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>*.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • 1
    "You cannot access a protected member of an object except when the said object is `*this`". This is not true. You can access protected and private members of any object whose static type is the same as the type of `*this`. That's why the call is possible after a static_cast to `C*`. Also, said static_cast is not illegal, just UB. – Balázs Kovacsics May 10 '19 at 15:23
  • whilst it seems an odd cast, and may be UB, it performs as desired on multiple compilers (MinGW, AVR-GCC). not saying it should be the correct way forwards, but it certainly works as a debugging tool if nothing else. – CommanderBubble May 14 '19 at 12:41
  • @BalázsKovacsics While what you say is true, I favored simplicity to exactitude in this answer. If you have an idea to be both accurate *and* simple, please suggest an edit. Note though that people who could find help in this Q&A might not be familiar with the concept of UB; this is why I used "illegal". – YSC May 14 '19 at 13:21
  • 2
    @CommanderBubble This kind of logic can drag you in unspeakable depths, beware. And [take this](https://stackoverflow.com/questions/54120862/does-the-c-standard-allow-for-an-uninitialized-bool-to-crash-a-program), it's dangerous to go alone. – YSC May 14 '19 at 13:22
  • That's an interesting rabbit hole. – CommanderBubble May 15 '19 at 09:42
3

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.

gaFF
  • 747
  • 3
  • 11
  • this would be OK, but i need to pass in a class derived from B, and the C only needs to guarentee conformity to the type B, but the overridden methods should still be called. hence my need for `m_Base` or `m_Self` – CommanderBubble May 14 '19 at 12:39
  • I think your class architecture could be simplified, could you elaborate your need ? Also, in disorder, `m_Self` looks similar to the pointer `this`, `m_Self` and `m_Base` point to the same object `b` but `C` has not the ownership which could lead to memory errors (seg fault), static down casting a pointer must always be done carefully (compared to dynamic casting) because there is little checking, and even dynamic down casting is considered a hint of architectural problem, passing the base class `A` to `C` while `C` has access to an object derived from `A` seems not necessary... – gaFF May 15 '19 at 07:39
-1

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

Moia
  • 2,216
  • 1
  • 12
  • 34
  • my example simplifies away some of the finer details of my actual problem, but suffice to say i need to be able to call any potentially overridden class functions, hence the need for the pointer. – CommanderBubble May 14 '19 at 12:42
  • @CommanderBubble you don't need the pointer to call any overridden class function in the hierarchy. And if you need them it is mostly a bad design smell. In any case, even if you actually need those pointer you didn't specify it was mandatory in your question hence there are no real reason for the downvote (and no error in what I wrote) – Moia May 14 '19 at 15:01
  • 1
    We can agree to disagree on reasons for my real world case. You are correct, I didn't specify it, but it was there because it was being used. However, the downvote isn't from me, I don't have the reputation to downvote. – CommanderBubble May 15 '19 at 09:41