我有以下C ++代码:
//Define to 1 to make it work
#define WORKS 0
#if WORKS
template< typename T > struct Foo;
#else
template< typename T >
struct Foo {
T t;
};
#endif
class Bar; //Incomplete type
void fFooBar(Foo<Bar> const & foobar) { }
void f(Foo<Bar> const & foobar) {
fFooBar(foobar);
}
int main() {
return 0;
}
如果WORKS定义为0(定义了结构模板),则代码不会编译,因为它会尝试在fFooBar(foobar);
实例化它并失败,因为Bar
不完整。< / p>
如果WORKS定义为1(结构模板未定义),则代码将编译。
根据标准,模板不应该被实例化,除非需要完整的类型(const&
不是这种情况)或者它会改变代码的语义(同样不是这样)。在这种情况下,如果模板未定义,则会发生相同的情况。)
此外,通过从编译单元中删除信息可以使程序编译,这很奇怪。但 MSVC,gcc和clang都做同样的事情这一事实让我觉得必须有一个理由背后。
答案 0 :(得分:5)
当WORKS=0
时,可以通过使用fFooBar
限定对::
的调用来使程序在Clang中编译。该标准要求在函数调用中使用非限定名称时,名称查找的行为会有所不同。
[basic.lookup.argdep] / 1
当函数调用(5.2.2)中的postfix-expression是非限定id时,其他名称空间不被考虑 在通常的非限定查找(3.4.1)期间,可以搜索,并在那些名称空间中,命名空间范围 可以找到不可见的朋友函数声明(11.3)。
检查Argument Dependent Lookup过程的(有些复杂的)规则表明它只能以需要在调用参数类型中实例化模板特化的方式正确实现。
[basic.lookup.argdep] / 2
对于函数调用中的每个参数类型T,都有一组零个或多个关联的命名空间和一个 要考虑的零个或多个关联类的集合。确定名称空间和类的集合 完全由函数参数的类型[...]
组成
- 如果T是类类型(包括联合),则其关联的类是:类本身;它所属的类 会员,如果有的话;及其直接和间接基类。
对此的一种解释是,如果在非限定函数调用的参数类型中使用类,则需要完成该类。另一种解释是ADL应该只导致完整模板的实例化。
根据N3337工作草案,这两种行为均符合标准
[temp.inst] / 6
如果重载解析过程可以在不实例化类的情况下确定要调用的正确函数 模板定义,未指定实例化是否实际发生。
template <class T> struct S {
operator int();
};
void f(int);
void f(S<int>&);
void f(S<float>);
void g(S<int>& sr) {
f(sr); // instantiation of S<int> allowed but not required
// instantiation of S<float> allowed but not required
};
[temp.inst] / 7
如果需要隐式实例化类模板特化并且声明模板但不是 定义,该计划是不正确的。