编译器为何允许用using指令为不完整类型的模板创建别名?
关于Attorney-Client惯用法中不完整类型使用的C++标准合规性问题
这段代码简化自Attorney-Client idiom(律师-客户惯用法),用于限制仅特定类可调用目标类的成员函数。以下是完整代码实现:
文件header_A.h
// forward declaration(前向声明) struct B; template <typename T> class Tag { friend T; // 仅T类型可构造tag Tag() = default; }; struct A { int val = 0; using tag = Tag<B>; // B是不完整类型 // 但在定义中使用该类型 // 仅B对象可调用此函数,因仅B能构造tag void Set(int i, const tag &) { val = i; } };
文件header_B.h
struct B { void Set(A& a, int i) { a.Set(i, Tag<B>{}); } };
文件main.cpp
#include "header_A.h" #include "header_B.h" int main() { A a; B b; b.Set(a,42); }
这段代码可正常编译运行,但存在以下疑问:
- 这段代码是否符合C++标准?
- 背后涉及哪些C++标准规则?
- 尤其在
B仍是不完整类型时,为何能编写using tag = Tag<B>;并在A的成员函数中使用该tag类型?我猜测是因为代码从未需要B的完整类型,但想了解具体原理。
解答
这段代码完全符合C++标准,核心依据以下几项标准规则:
模板参数允许不完整类型
C++标准规定,若模板的定义不需要模板参数的完整类型,则可以用不完整类型作为模板实参。在Tag<B>中,Tag类仅将T(即B)声明为友元——友元声明只需要类型名称,无需类型完整;同时Tag的默认构造函数也不需要访问B的成员或知晓其大小。因此在header_A.h中声明using tag = Tag<B>时,B虽为不完整类型,但此时Tag<B>并未被实例化,仅作为类型别名存在。成员函数的延迟实例化
A::Set的参数是const tag&(即const Tag<B>&),但成员函数的内联定义不会在struct A定义时立即实例化Tag<B>的完整类型。只有当A::Set被实际调用(如B::Set中调用a.Set(i, Tag<B>{}))时,才会触发Tag<B>的实例化,而此时B已在header_B.h中完成完整定义,满足实例化的要求。友元权限与类型依赖的配合
Tag类的设计利用了友元的访问控制:只有B能构造Tag<B>对象,因此只有B可以调用A::Set(该函数要求Tag<B>类型的参数)。整个过程中,A的定义阶段仅需B的前向声明,因为此时不需要Tag<B>的完整定义,仅引入类型别名即可。
内容的提问来源于stack exchange,提问作者Oersted
相关产品推荐
相关产品推荐

