编译器如何检查未实例化的模板代码?

What does a compiler check for uninstantiated template code?

例如,以下代码片段使用 gcc-4.9 和 clang-602 编译

class Base                                                                      
{                                                                               
public:                                                                         
    static void foo() {}                                                        
    void badfoo(int i) {}                                                       
};                                                                              

template <typename T>                                                           
class Derived : public Base                                                     
{                                                                               
public:                                                                         
    void bar() { Base::foo(); }                                                 
    void badbar() { Base::badfoo(); }  // compiles ok
    //static void badbar() { Base::badfoo(); }  // compile error                                                                                    
    //void worsebar() { Base::nonexist(); }  // compile error                                   
};                                                                              

int main()                                                                      
{                                                                               
    return 0;                                                                   
}  

但是注释掉的行不会编译。

我的问题是:

  1. 为什么 badbar() 编译但 worsebar() 不编译?

  2. 如果我将 badbar() 更改为静态,它也不会编译,无论 base::badfoo 是否为静态。

  3. 标准是否说明了在这种情况下应检查的内容? gcc4.4 实际上甚至拒绝编译 badbar().

更新:

问题 1 已通过许多答案进行了解释,但似乎编译器有时也会更加努力地检查过载,它发生在 gcc 4.4.3 和 4.8.2 上,但不是 4.7.2 和 4.9 .1.

问题 2:正如 Marco A. 指出的那样,clang 无法编译,但 gcc4.9 仍会通过。但是,gcc4.2 和 gcc4.4 都拒绝该代码,并且他们抱怨的错误是 "no matching function" 而不是 clang 中的 "calling non-static member without an object"。这个问题似乎没有确凿的答案,所以我按照 Daniel Frey 的建议添加了一个语言律师标签。

更多更新:

我认为对于问题 2,答案现在已经很清楚了:添加静态声明是否会改变诊断取决于编译器。它因编译器和编译器的不同以及同一编译器的不同版本而异。语言律师没有出现,我将接受 Daniel Frey 的回答,因为它最好地解释了第一个问题。但 Marco A. 和 Hadi Brais 的回答也值得一读。

考虑 [temp.res]/8:

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

这(特别是 "no diagnostic required" 位)使任何编译器的行为都符合 worsebar。实现对此类代码的差异只是 QoI 问题——普通编译器会进行一些分析并会抱怨。很难说确切的时间,您应该准备好在升级或切换实现时返回到模板代码。

首先,如果您要实例化 Devired 并尝试调用 badbar,则会出现编译错误:

// ...
int main()                                                                      
{                                                                               
    Derived<int> d;
    d.badbar();
    return 0;                                                                   
}

产生

error: too few arguments to function call, single argument 'i' was not specified
void badbar() { Base::badfoo(); }  // compiles ok
                ~~~~~~~~~~~~ ^

编译器不编译未实例化的代码。

标准只要求名称查找发生在第 1 阶段,即首次解析模板时。这在 badbar 中出现 badfoo,这就是代码编译的原因。那时编译器不需要做重载决议。

然后在 badbar 被实例化时在第 2 阶段执行重载解析(它总是作为单独的步骤 名称查找之后发生) - 这不是在你的例子中。这个原理可以在

中找到

3.4 Name lookup [basic.lookup]

1 The name lookup rules apply uniformly to all names (including typedef-names (7.1.3), namespace-names (7.3), and class-names (9.1)) wherever the grammar allows such names in the context discussed by a particular rule. Name lookup associates the use of a name with a declaration (3.1) of that name. Name lookup shall find an unambiguous declaration for the name (see 10.2). Name lookup may associate more than one declaration with a name if it finds the name to be a function name; the declarations are said to form a set of overloaded functions (13.1). Overload resolution (13.3) takes place after name lookup has succeeded. The access rules (Clause 11) are considered only once name lookup and function overload resolution (if applicable) have succeeded. Only after name lookup, function overload resolution (if applicable) and access checking have succeeded are the attributes introduced by the name’s declaration used further in expression processing (Clause 5).

(强调我的)

因此我认为编译器接受代码是正确的,尽管我不确定他们是否需要这样做。

要查看代码 being rejected,您需要实例化 badbar

在实例化任何类型或发出任何代码之前,编译器会逐步构建一个包含所有已声明符号的 table。如果使用了未声明的符号,则会发出错误。这就是 worsebar 无法编译的原因。另一方面,badfoo 已声明,因此 badbar 可以编译。在编译过程的早期阶段,编译器不会检查对 badfoo 的调用是否与声明的 badfoo 实际匹配。

由于 Derived 类型在代码中的任何地方都没有被实例化,编译器不会发出任何关于它的代码。特别是,badbar 将被忽略。

现在,当您声明 Derived 的实例(例如 Derived< int >)但不使用其任何成员时,编译器将只创建一个包含那些已被使用的成员的类型,而忽略其他成员。仍然没有关于 badbar 的错误。

但是,当声明 Derived 的实例并调用 badbar 时,需要实例化 badbar 方法,因此编译器将创建一个带有 badbar 的类型并对其进行编译。这一次,编译器注意到 badfoo 实际上并没有被声明,因此发出了一个错误。

此行为记录在 C++ 标准的第 14.7.1 节中。

Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist.

最后,如果 badbar 是静态的并且被编译器实例化(因为它已经被使用)那么编译器将发出一个错误 badfoo 不存在。现在,如果您将整数参数传递给 badfoo,则会发出另一个错误,指示静态方法无法访问实例成员,因为首先没有实例。

编辑

编译器没有义务不报告未实例化模板类型中的语义错误。该标准只是说它不必,但可以。关于在哪里划清界限是可以讨论的。请参阅 this clang 中有关相关问题的讨论:

which uninstantiated templates do we analyze? For performance reasons, I don't think we should analyze all the uninstantiated templates, as we may find ourselves repeatedly analyzing a huge portion of the Boost and the STL, etc.

因此,未实例化的模板分析会随着不同版本的 clang 和 gcc 以不同的方式发生变化。但同样,按照标准:There's no requirement to report errors in uninstantiated templates, of course.

为了清楚起见.. 这个版本的代码在 clang 和 gcc 上编译得很好

class Base                                                                      
{                                                                               
public:                                                                         
    static void foo() {}                                                        
    void badfoo(int a) {}                                                       
};                                                                              

template <typename T>                                                           
class Derived : public Base                                                     
{                                                                               
public:                                                                         
    void bar() { Base::foo(); }                                                 
    void badbar() { Base::badfoo(); }   
}; 

[temp.res]/p8

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

gcc 和 clang 都不需要诊断这个。这个也和上面一样(clang报错,gcc不报错)

class Base                                                                      
{                                                                               
public:                                                                         
    static void foo() {}                                                        
    void badfoo(int a) {}                                                   
};                                                                              

template <typename T>                                                           
class Derived : public Base                                                     
{                                                                               
public:                                                                         
    void bar() { Base::foo(); }                                                 
    static void badbar() { Base::badfoo(); } 
}; 

案例

void worsebar() { Base::nonexist(); }

不同,因为它违反了名称查找 [temp.res]/p9

When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1, 3.4.2) are used for non-dependent names

[temp.res]/p10

If a name does not depend on a template-parameter (as defined in 14.6.2), a declaration (or set of declarations) for that name shall be in scope at the point where the name appears in the template definition

免责声明:以上所有不适用于 MSVC,MSVC 很乐意将所有这些内容推迟到查找的第二阶段。


编辑:

class Base                                                                      
{                                                                               
public:                                                                         
    static void foo() {}                                                        
    void badfoo(int i) {}                                                       
};                                                                              

template <typename T>                                                           
class Derived : public Base                                                     
{                                                                               
public:                                                                         
    static void badbar() { Base::badfoo(); }  // static function

clang triggers an error while gcc doesn't。这属于第一种情况,因为名称查找成功但 clang 执行额外的检查:因为 badfoo 是一个成员函数,它试图构造一个有效的隐式成员引用表达式。然后它捕捉到一个成员函数被静态函数隐式调用的事实,并检测上下文不匹配。这个诊断在这一点上完全取决于编译器(它不会在实例化的情况下)。