C++模板使用不完整类型合法性:实例化时类型完整是否合规
C++模板两阶段查找规则下该代码的合法性判定
你的代码不符合C++标准要求,属于非法代码,三种编译器的表现差异本质是对两阶段查找规则的严格程度不同,Clang的默认行为是完全符合标准的。
核心判定依据:模板两阶段查找规则
C++标准明确要求模板的名称查找分两个阶段执行:
- 模板定义阶段:编译器会对所有不依赖模板参数的名称做完整的语法、类型检查,这些检查不会延迟到实例化阶段执行。
- 模板实例化阶段:仅对依赖模板参数的名称做检查,此时会使用实例化时传入的实际类型做校验。
对应到你的示例代码:foo::frobnicate里的b->frobnicate()调用中,b的类型是bar*,和模板参数T没有任何关联,属于非依赖名称,必须在模板定义阶段完成检查。而模板定义时bar只有前置声明,属于不完整类型,不允许访问其成员,因此标准要求此处必须给出编译错误。
不同编译器表现差异的原因
- Visual Studio:默认没有实现严格的两阶段查找,所有名称检查都延迟到了模板实例化阶段,此时
bar已经是完整类型,因此不会报错。如果开启/permissive-编译选项启用严格标准兼容模式,VS也会抛出和Clang一致的错误。 - GCC:实现了部分两阶段检查,能识别到模板定义阶段
bar为不完整类型,但为了兼容大量历史非标准代码,仅输出警告而不终止编译,属于对非标准代码的放宽兼容。 - Clang:默认实现了严格的两阶段查找,完全遵循标准要求,因此直接抛出错误终止编译,是最符合标准规范的行为。
标准兼容的代码修正方案
只要把frobnicate函数的实现移动到bar的完整定义之后即可,调整后的代码如下:
#include <iostream> struct bar; template <typename T> struct foo { foo(bar* b) : b(b) { } // 仅保留成员函数声明 void frobnicate(); T val_; bar* b; }; struct bar { void frobnicate() { std::cout << "foo\n"; } }; // bar已经为完整类型后再实现frobnicate函数 template <typename T> void foo<T>::frobnicate() { b->frobnicate(); } int main() { bar b; foo<int> f(&b); f.frobnicate(); return 0; }
修改后的代码在三大编译器下都可以正常编译运行,完全符合C++标准要求。
内容的提问来源于stack exchange,提问作者jwezorek
相关产品推荐
相关产品推荐

