c++templatesinheritanceprotectedqualified-name

Why is there an access error when trying to access a protected member of a base template class?


It works fine with regular classes:

class Base
{
public:
    Base() {}
protected:
    int* a;
};

class Derived : public Base
{
public:
    Derived() {}
    void foo() {
        int** pa = &a;
    }
};

int main() {
    Derived* d = new Derived();
    d->foo();
    delete  d;
}

But it reports an error when Base and Derived classes use templates:

‘int* Base<int>::a’ is protected within this context

template<typename T>
class Base
{
public:
    Base() {}
protected:
    int* a;
};

template<typename T>
class Derived : public Base<T>
{
public:
    Derived() {}
    void foo() {
        int** pa = &Base<T>::a;
    }
};

int main() {
    Derived<int>* d = new Derived<int>();
    d->foo();
    delete d;
}

Why is that?


Solution

  • The error is mostly unrelated to templates, and occurs also without any inheritance. The simple issue is that the expression &Base<T>::a is parsed as a pointer to member, as the following snippet shows:

    #include <iostream>
    #include <typeinfo>
    using namespace std;
    
    class B
    {
    public:
        void foo()
        {
            int* B::* pa = &B::a;
            int** pi = &(B::a);
    
            cout << typeid(pa).name() << endl;
            cout << typeid(pi).name() << endl;
        }
    
    protected:
        int* a;
    };
    
    struct D : public B
    {
        // Access to B::a is perfectly fine.
        int* B::* pa = &B::a;
    
        // But this causes a type error:
        // "cannot convert from 'int *B::* ' to 'int **'
        // int** pi = &B::a;
        
        // Parentheses help to get the address of this->a ...
        int** pi2 = &(B::a);
    
        // ... and writing this->a helps, too ;-).
        int **pi3 = &this->a;
    
        // Of course, outside of templates we can simply write a!
        int** pi4 = &a;
    };
    
    int main()
    {
        B b;
        b.foo();
    }
    

    The output is:

    int * B::*
    int * *
    

    Templates are where the error surfaces because we are forced to qualify dependent names and therefore unintentionally end up with a pointer-to-member construct.

    Both solutions in the comment section work: You can simply write &this->a or, as I did here, put the qualified member in parentheses. Why the latter works is not clear to me: operator::() has the single highest precedence, so the parentheses do not change that.

    It is, as one would expect, perfectly possible to take the address of a protected base class member in a derived class. The error message when templates are involved was, as far as I can see, incorrect and misleading (but then I'm usually wrong when I think it's the compiler's fault...).