为什么以下代码会导致模板实例化?

Giu*_*nco 11 c++ templates

我有以下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;
}
Run Code Online (Sandbox Code Playgroud)

如果WORKS被定义为0(定义了结构模板),则代码不会编译,因为它尝试将其实例化fFooBar(foobar);并失败,因为它Bar是不完整的.

如果WORKS定义为1(结构模板未定义),则代码将编译.

根据标准,模板不应该被实例化,除非需要一个完整的类型(事实并非如此const&)或者它会改变代码的语义(事实并非如此),同样,应该如果模板未定义则发生).

而且,通过从编译单元中删除信息可以使程序编译,这很奇怪.但MSVC,gcc和clang都这样做的事实让我觉得必须有这背后的原因.

wil*_*llj 5

WORKS=0,可以使程序在锵由呼叫排位编译fFooBar使用::.该标准要求在函数调用中使用非限定名称时,名称查找的行为会有所不同.

[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
};
Run Code Online (Sandbox Code Playgroud)

[temp.inst]/7

如果需要隐式实例化类模板特化并且声明模板但未定义模板,则程序格式错误.