模板类中嵌套非模板私有类的友元函数编译问题咨询
关于模板私有内部类的友元访问问题
原问题代码及编译错误
head.h
template <class T> class Ext; template <class T> void fun(const Ext<T>&); template <class T> class Ext { friend void fun<T>(const Ext<T>&); private: class Inner; Inner* p; }; template <class T> void fun2(const typename Ext<T>::Inner&); template <class T> class Ext<T>::Inner { friend class Ext<T>; friend void fun<T>(const Ext<T>&); friend void fun2<T>(const typename Ext<T>::Inner&); private: Inner* q; int a; Inner() : a(1) {} }; template <class T> void fun2(const typename Ext<T>::Inner& i) { std::cout << i.a << std::endl; } template <class T> void fun(const Ext<T>& e) { typename Ext<T>::Inner y; y.a = 3; fun2<T>(y); }
main.cpp
int main() { Ext<int> x; fun(x); }
编译报错(g++和clang均触发):
class Ext
::Inner’ is private within this context
问题
- 有没有办法在不将
Inner类设为public的前提下解决该问题? - 为何将
fun和fun2替换为重载的operator<<后,仅在clang下可以编译通过(Inner类仍为private)?对应的示例代码如下:
运算符重载示例代码
head.h
#include <iostream> template <class T> class Ext; template <class T> std::ostream& operator<<(std::ostream&, const Ext<T>&); template <class T> class Ext { friend std::ostream& operator<< <T>(std::ostream&, const Ext<T>&); private: class Inner; Inner* p; }; template <class T> std::ostream& operator<<(std::ostream&, const typename Ext<T>::Inner&); template <class T> class Ext<T>::Inner { friend class Ext<T>; friend std::ostream& operator<< <T>(std::ostream&, const typename Ext<T>::Inner&); private: Inner* q; int a; Inner() : a(1) {} }; template <class T> std::ostream& operator<<(std::ostream& os, const typename Ext<T>::Inner& n) { return os << n.a << " "; } template <class T> std::ostream& operator<<(std::ostream& os, const Ext<T>& t) { os << "[ "; operator<< <T>(os, *(t.p)); os << "] "; return os; }
main.cpp
int main() { Ext<int> x; std::cout << x << std::endl; }
解答
问题1:不公开Inner类的解决方法
报错核心是:Inner是Ext<T>的私有嵌套类,虽然fun<T>是Ext<T>的友元,但友元函数只能访问外部类的私有成员,无法直接引用外部类私有嵌套类型的名称——C++标准中,私有嵌套类的类型名本身对外部不可见,哪怕是友元函数也不能直接用这个类型名声明变量。
有两种合法解决方案:
方案1:将fun2声明为Ext的友元
修改Ext<T>的定义,把fun2的模板声明放入Ext<T>内部并友元化,让它能合法访问Inner的类型名:
template <class T> class Ext; template <class T> void fun(const Ext<T>&); template <class T> void fun2(const typename Ext<T>::Inner&); template <class T> class Ext { friend void fun<T>(const Ext<T>&); // 友元化fun2模板 template <class U> friend void fun2(const typename Ext<U>::Inner&); private: class Inner; Inner* p; };
方案2:通过Ext的接口间接创建Inner对象
在Ext<T>内部添加静态成员函数,用于创建Inner对象,避免fun直接引用私有嵌套类型名:
template <class T> class Ext { friend void fun<T>(const Ext<T>&); private: class Inner; Inner* p; // 静态方法返回Inner对象 static Inner create_inner() { return Inner(); } }; // 修改fun的实现 template <class T> void fun(const Ext<T>& e) { auto y = Ext<T>::create_inner(); y.a = 3; fun2<T>(y); }
问题2:clang允许operator<<编译的原因
这是不同编译器对C++标准细节的实现差异:
- C++标准中,即使
operator<< <T>是Inner的友元,理论上它也不能直接引用Ext<T>::Inner的类型名(因为类型名的可见性独立于成员访问权限)。 - clang对运算符重载做了宽松扩展:当运算符函数被明确声明为私有嵌套类的友元时,编译器判定它可以访问嵌套类的类型名。
- g++严格遵循标准,认为私有嵌套类的类型名对外部(包括友元函数)不可见,因此触发编译错误。
简单来说,clang的实现属于标准外的宽松处理,而g++严格执行标准,所以只有clang能编译通过该代码。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

