以下是一段测试代码,我将分别编译它与MSVC和Clang的结果进行比较。每个编译器的输出如下所示。 MSVC假装未使用的模板声明甚至不存在。 Clang产生错误。问题是,哪个编译器最符合标准?
我见过依赖于MSVC行为的遗留生产代码,我不确定它是否可以继续依赖。
class S
{
struct P {};
};
template<typename T>
S::P Bat(T);
在MSVC10中完全编译:
E:\clangbuild\bin\Release>cl /c /nologo test.cpp
test.cpp
在Clang中产生错误:
E:\clangbuild\bin\Release>clang++ test.cpp
test.cpp:9:4: error: 'P' is a private member of 'S'
S::P Bat(T);
^
test.cpp:5:9: note: implicitly declared private here
struct P {};
^
1 error generated.
答案 0 :(得分:4)
由于在C ++中进行两阶段名称查找,因此失败。
在第一阶段,当模板最初被解析时,早在实例化之前,编译器就会解析模板并查找任何非相关名称。 S::P
是一个非依赖名称,因此编译器会尝试查找它,但由于它是私有的而失败。
在阶段2中,当模板被实例化时,编译器将查找任何依赖名称,这些名称可能因模板而异。
Clang非常严格地符合两阶段名称查找。但是,MSVC有一个模板解析模型,它几乎将每个查找延迟到实例化时间,这是第2阶段的一部分。这个延迟是你的例子用MSVC(不符合条件)而不是clang编译的原因。这是一个包含更多信息的链接:
The Dreaded Two-Phase Name Lookup
此外,以下是C ++标准中描述两阶段查找的部分。
14.6.8:
查找模板中使用的名称声明时 定义,通常的查找规则(3.4.1,3.4.2)用于 非依赖名称。查找依赖于模板的名称 参数被推迟,直到知道实际的模板参数。
14.6.9:
如果名称不依赖于模板参数(如中所定义) 14.6.2),该名称的声明(或一组声明)应在名称出现在模板中的范围内 定义;名称与声明(或声明)绑定 在那一点上找到并且此绑定不受声明的影响 在实例化时可见。
然后3.4名称查找的部分适用于您:
访问规则(第11条)仅被视为名称查找和 函数重载决策(如果适用)已成功。只要 名称查找后,函数重载解析(如果适用)和 访问检查成功是由...引入的属性 name的声明在表达式处理中进一步使用(第5条)。
从阅读这些部分可以清楚地看出你的程序是不正确的。标准规定应该推迟到实例化的唯一事情是查找依赖名称。非依赖名称通过通常的名称查找,其中包括访问规则。
答案 1 :(得分:3)
只需要编译器检查未实例化的模板声明的任何格式错误的语法。只有在实例化模板函数时才需要进行任何额外的语义评估。
由于S :: P确实是一个有效从函数返回的类型,因此它们同样符合要求。