4

So I've read through this question, and I understand the premise of dependent names in templates, and how you must sometimes qualify a method with this-> to ensure that the compiler can properly find it, however I've run into a scenario that I can't figure out how to solve. Specifically, when the object that the method belongs to is of the same type as *this, but it's a different object (potentially of a different subclass). For example:

#include <iostream>

template<class T>
class A
{
protected:
    virtual void foo() = 0;
};

template<class T>
class B : public A<T>
{
};

template<class T>
class C : public B<T>
{
protected:
    void foo() override
    {
        std::cout << "Hello, world" << std::endl;
    }
};

template<class T>
class D : public B<T>
{
protected:
    void foo() override
    {
        B<T> *b = new C<T>();
        b->foo(); // error: 'void A<T>::foo() [with T = int]' is protected
    }
public:
    void bar()
    {
        this->foo();
    }
};

int main()
{
  D<int> d;
  d.bar();
}

Given this inheritance hierarchy, and the error when calling b->foo();, what would be the proper way to call that function? As I understand, it should in principle be accessible to code in D, as it's a protected member of a base class, but it's complicated by the template system.

Nick Mertin
  • 1,149
  • 12
  • 27
  • 2
    `C` and `D` are siblings hence cannot access each others' protected members. Only parent-> child hierarchies can. So what is happening in your code is that you call publicly available function through parent's interface, but actual implementation is inaccessible for the caller by child's class. Seems like it has nothing to do with templates. Actually, very interesting situation, also interesting that compiler caught it :) – Alexey Andronov Jun 04 '18 at 02:26
  • @AlexeyAndronov sorry, I don't understand... Isn't foo a member of A, which is a parent of D? – Nick Mertin Jun 04 '18 at 02:49
  • Yes, this is why your `bar` function properly calls `D::foo`. Problems begin when you create `C` object and try calling it's public virtual method `A::foo` actual implementation of which (`C::foo`) is inaccessible for any caller (except children of `C`). To avoid confusion you can move `D::foo` code to main and you'll get the same error – Alexey Andronov Jun 04 '18 at 03:12

1 Answers1

2

Your problem has nothing to do with templates and gives the same error if you remove all templating code.

Declaring a member as protected means that you can only access that member from the same (direct) class; so while D and C might both derive from B, they are both distinctly different types and not directly a B, thus D and C cannot access the protected members of another instance of a B.

As an example, take the following code using some of your posted code:

class D : public B
{
    public:
        void baz(D& d, B& b)
        {
            foo(); // ok: calls this->foo();
            d.foo(); // ok: type of `d` is `D`, same class
            b.foo(); // error: can't access protected member
        }
};

In your code, changing B* b = new C(); to B* b = new B(); in the D class will still get the same error trying to access a protected member. If you change it to D* b = new D();, then the error goes away because b is now the same type as the class it's within.

To address this error, you can declare D a friend of B, but that introduces other issues to consider, as well, you have to add a forward/friend declaration for every class you want to have access to foo in this manner:

template < class T >
class D;

template <class T>
class B : public A<T>
{
    friend class D<T>;
};

However, given the code you've posted, one would be inclined to instead move the bar function out of D and put it in B, then you don't have to muck with forward declarations and most of your code stays the same:

template <class T>
class B : public A<T>
{
public:
    void bar()
    {
        this->foo();
    }
};

// Then in your D class, change the call from `foo` to `bar`

template <class T>
class D : public B<T>
{
protected:
    void foo() override
    {
        B<T> *b = new C<T>();
        b->bar();
    }
};

This might not fit your specific scenario though, but in the end your issue basically comes down to needing public access.

Hope that can help.

txtechhelp
  • 6,625
  • 1
  • 30
  • 39
  • Thank you, this mostly makes sense to me. One note from the link you included: "by the members and friends (until C++17) of any class derived from Base, but only when operating on an object of a type that is derived from Base (including this)". Why doesn't this include virtual functions? – Nick Mertin Jun 04 '18 at 11:55
  • @NicholasMertin, virtual functions are included in that, or am I misunderstanding what you are confused on with regards to that? A member being virtual or not won't change it's access specifier. I can update my answer to give more clarity on that or provide more examples if that would help? – txtechhelp Jun 05 '18 at 00:13