Fuzzy behavior of a friend of a C ++ template

I see something that I cannot explain in the following code. In VS6, VS9 and GCC T2 :: foo2 () throws an error: "bar": cannot access the protected member declared in class "C1". But if you delete C1 :: bar (), it compiles and works correctly, although T2 still accesses the protected C1B: bar (), which, in your opinion, will be the same problem.

Note that in T2 :: foo2 () you can use "pT1" as "T1 *" and everything is fine, but that still doesn’t explain why C1B :: bar () is allowed, but C1 :: bar () 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 } 
+6
source share
1 answer

In your example, the parameter type S in foo2 is C1 . A friendly relationship exists between T2<S> and T1<S> . Although C1 comes from T1<C1> , he does not become a friend of all friends of T1<C1> .

In the expression pT1->bar , bar is in C1 and, since friendship is not passed to the derived class, it is private.

Your example uses virtual functions, so this can be fixed by explicitly referring to bar , which is a friend of our class:

 void foo2(S *pT1) { pT1->template T1<S>::bar(); } 

Access check is now verified.

The reference to this in the '03 C ++ standard is at 11.4 / 10, where it simply says:

Friendship is neither inherited nor transitive.

Thanks to Potatoswatter for his comment. Using qualified-id for id-expession , we will disable virtual sending (5.2.2 / 1):

If the selected function is not virtual, or if the id expression in the class member access expression is a qualification identifier, this function is called.

We can add a non-virtual dispatcher, or we can first convert the parameter to the base type, and then make a call:

  void foo2(S *pT1) { static_cast< T1<S>* > (pT1)->bar(); } 

Virtual sending is now performed as needed.

+9
source

Source: https://habr.com/ru/post/887864/


All Articles