友元函数在MSVC可编译但GCC、Clang报错,求合规解释与兼容方案
标准规定的正确行为
Outer<T>::Inner是私有嵌套类型,只有被明确声明为友元的代码才能访问它——包括把它用作函数参数的类型。
你在Outer<T>::Inner里写的友元声明template<typename S> friend void bar(typename Outer<S>::Inner);,意思是:所有满足bar<S>(Outer<S>::Inner)形式的模板实例,都是当前Inner的友元。但你实际定义的bar模板是template<typename S> void bar(typename Outer<int>::Inner),它的参数是固定死的Outer<int>::Inner,和友元声明里参数随S变化的bar<S>完全不匹配。
所以这个bar模板根本没获得访问Outer<int>::Inner的权限,GCC和Clang报错是符合C++标准的,MSVC的编译通过属于它自己的非标准扩展,不具备通用性。
另外你注释掉的typename Outer<int>::Inner k;在所有编译器都报错,原因是:哪怕bar能通过参数用到这个类型(如果是友元的话),但直接在函数里定义该类型的对象,同样需要bar是Outer<int>::Inner的友元——显然当前的bar模板不满足,所以全报错。
兼容所有编译器的修改方案
方案1:让bar模板参数与友元声明匹配
调整bar的定义,使其参数依赖于模板参数S,这样就能匹配Inner中的友元声明:
#include <iostream> template<typename T> class Outer { struct Inner; friend void foo(); }; template<typename T> struct Outer<T>::Inner { template<typename S> friend void bar(typename Outer<S>::Inner); }; // 修改bar的参数为Outer<S>::Inner,匹配友元声明 template<typename S> void bar(typename Outer<S>::Inner) { // 现在如果S=int,这里可以合法定义Inner对象 typename Outer<S>::Inner k; std::cout << "bar called\n"; } void foo() { std::cout << "foo called\n"; Outer<int>::Inner i; bar<int>(i); } int main() { Outer<int> outer; foo(); }
这种方式下,bar<int>会被Outer<int>::Inner的友元声明覆盖,因此能合法访问私有类型。
方案2:针对特定类型声明非模板友元
如果确实需要bar的参数固定为Outer<int>::Inner,可以在Inner的定义中专门声明该非模板函数为友元:
#include <iostream> template<typename T> class Outer { struct Inner; friend void foo(); }; template<typename T> struct Outer<T>::Inner { // 针对特定的bar函数声明友元 friend void bar(typename Outer<int>::Inner); }; // 定义非模板函数bar void bar(typename Outer<int>::Inner) { std::cout << "bar called\n"; } void foo() { std::cout << "foo called\n"; Outer<int>::Inner i; bar(i); } int main() { Outer<int> outer; foo(); }
这种方式更直接,适用于只需要针对特定Outer实例的Inner类型的场景。
内容的提问来源于stack exchange,提问作者user20562802

