1

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?

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
KindFrog
  • 358
  • 4
  • 17
  • 2
    Fyi, `&Base::a` ==> `&(Base::a);` is prolly what you're shooting for. – WhozCraig Aug 30 '21 at 20:25
  • 1
    use `void foo() { int** pa = &(this->a); }` – asmmo Aug 30 '21 at 20:30
  • @WhozCraig That is a difference!? – Peter - Reinstate Monica Aug 30 '21 at 20:36
  • @asmmo And the reason has to do with the arcane rules of dependent name lookup in templates, I suppose. Can anybody construct a case where `int** pa = &Base::a` becomes illegal in a certain specialization? – Peter - Reinstate Monica Aug 30 '21 at 21:06
  • @WhozCraig I'm puzzled. operator::() has the highest precedence. How can the parentheses make a difference? – Peter - Reinstate Monica Aug 30 '21 at 21:11
  • 1
    To the OP: The exact error occurs when you qualify the name in the non-template classes: `&this->a` works, `&(B::a);` works (I called the base class `B`), but `&B::a;` does not. That's puzzling because `operator::()`has the highest precedence of all C++ operators. I think `error C2440: 'initializing': cannot convert from 'int *B::* ' to 'int **'` gives a hint: Does the original code mean "pointer to member"? g++ has a similar wording: `error: cannot convert ‘int* B::*’ to ‘int**’ in initialization`. – Peter - Reinstate Monica Aug 30 '21 at 21:16

1 Answers1

3

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

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62