简体   繁体   中英

C++ Template friend odd behavior

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
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM