I'm seeing something I can't explain in the following code. Under VS6, VS9, and GCC T2::foo2() gives the error: 'bar' : cannot access protected member declared in class 'C1'. But if you remove C1::bar(), it compiles and runs correctly, even though T2 is still accessing the protected C1B:bar(), which you would think would be the same problem.
Note, that in T2::foo2() you could cast 'pT1' to be a 'T1*' and everything is fine, but that still does not explain why C1B::bar() is allowed, but C1::bar() is not.
template<class S> class T2;
template<class T> class T1
{
//template<class T> friend class T2; --> this doesn't compile under VS6
friend class T2<T>;
protected:
virtual void bar() { printf("T1\n"); }
};
template<class S> class T2
{
public:
void foo1(T1<S> *pT1) { pT1->bar(); } // --> ok, makes sense, this works either way
void foo2(S *pT1) { pT1->bar(); } // --> this fails to compile if C1::bar() is defined, but works for C1B::foo() ???
};
class C1 : public T1<C1>
{
protected:
virtual void bar() { printf("C1\n"); } // --> comment this out and foo2 will compile
};
class C1B : public C1
{
protected:
virtual void bar() { printf("C1B\n"); }
};
class C2 : public T2<C1>
{
};
void test(void)
{
C1B c1b;
C2 c2;
c2.foo1(&c1b);
c2.foo2(&c1b); // --> fails to compile if C1::bar() exists
}
Template friends. A template friend declaration can name a member of a class template A, which can be either a member function or a member type (the type must use elaborated-type-specifier ). Such declaration is only well-formed if the last component in its nested-name-specifier (the name to the left of the last ::) is a simple-template-id...
In friend declaration operator<< refers to a non-template function, while its definition says it's a template function; they don't match. You can define it inline with the friend declaration (as non-template function):
The friend function declaration "f" will cause an error when the enclosing template class is instantiated with arguments that declare a friend function that does not match an existing definition. The function declares only one function because it is not a template but the function type depends on one or more template parameters.
Default template arguments are only allowed on template friend declarations if the declaration is a definition and no other declarations of this function template appear in this translation unit.
In your example, the type of the parameter S
in foo2
is C1
. The friend relationship exists between T2<S>
and T1<S>
. Although C1
derives from T1<C1>
it does not become a friend of all the friends of T1<C1>
.
In the expression pT1->bar
, bar
is found in C1
and, as the friendship is not passed down to the derived class, it is private.
Your example is using virtual functions so this can be addressed by explicitly referring to the bar
that is a friend of our class:
void foo2(S *pT1) { pT1->template T1<S>::bar(); }
The access check now succeeds.
The reference for this in the '03 C++ standard is in 11.4/10, where it simply says:
Friendship is neither inherited nor transitive.
Thanks to Potatoswatter for his comment. By using the qualified-id
for the id-expession
, we disable virtual dispatch (5.2.2/1):
If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called.
We can add a non-virtual dispatcher, or we can first convert the parameter to the base type and then make the call:
void foo2(S *pT1) { static_cast< T1<S>* > (pT1)->bar(); }
Virtual dispatch now takes place as required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With