编译器检查未实例化的模板代码是什么?

时间:2015-05-14 08:14:52

标签: c++ templates gcc clang language-lawyer

例如,以下代码段使用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()

  4. 更新:

    问题1已被许多答案解释,但似乎编译器有时会加倍检查过载,它发生在gcc 4.4.3和4.8.2,但不是4.7.2和4.9.1

    问题2:正如Marco A.指出的那样,clang不会编译,但gcc4.9仍会通过。但是,gcc4.2和gcc4.4都拒绝代码,并且他们抱怨的错误是“没有匹配的函数”,而不是在clang中“调用没有对象的非静态成员”。这个问题似乎没有确凿的答案,所以我正在添加一个语言律师标签,正如Daniel Frey建议的那样。

    更多更新:

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

5 个答案:

答案 0 :(得分:14)

标准只需要在第一阶段解析时进行名称查找。这会在badfoo中显示badbar,这就是代码编译的原因。此时编译器不需要执行重载解析。

在实例化badbar时,在阶段2中执行重载决策(在名称查找后,它总是作为单独的步骤执行) - 在您的示例中不是这种情况。这个原则可以在

中找到
  

3.4名称查找[basic.lookup]

     

1 名称查找规则统一适用于所有名称(包括 typedef-names (7.1.3), namespace-names (7.3 )和 class-names (9.1))语法允许在特定规则讨论的上下文中的这些名称。名称查找将名称的使用与该名称的声明(3.1)相关联。名称查找应找到名称的明确声明(见10.2)。如果名称查找名称是函数名称,则名称查找可以将多个声明与名称相关联;据说声明形成一组重载函数(13.1)。 重载解析(13.3)在名称查找成功后发生。访问规则(第11条)仅被视为名称查找和功能重载解析(如果适用)成功。只有在名称查找之后,函数重载解析(如果适用)和访问检查成功才会在表达式处理(第5章)中进一步使用名称声明引入的属性。

(强调我的)

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

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

答案 1 :(得分:7)

考虑[temp.res] / 8:

  

如果无法为模板生成有效的专业化,那么   模板没有实例化,模板格式不正确,没有   需要诊断。

这(特别是“无需诊断”位)使得任何编译器的行为都符合worsebar。实现在这种代码上的差异只是QoI问题 - 常见的编译器做了一些分析并会抱怨。很难说具体到什么时候,你应该准备好在升级或切换你的实现时回到模板代码。

答案 2 :(得分:3)

为了清楚一点..这个版本的代码在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

  

如果没有有效的专业化可以   为模板生成,并且该模板未实例化,模板格式错误,无法诊断   必需的。

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

  

在查找模板定义中使用的名称声明时,通常的查找规则(3.4.1,   3.4.2)用于非依赖名称

[temp.res] / p10

  

如果名称不依赖于模板参数(如14.6.2中所定义),则声明(或声明集)   该名称应在名称出现在模板定义

中的范围内

免责声明:以上所有内容均不适用于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
{p} clang triggers an error gcc doesn't。这是第一种情况,因为名称查找成功但clang执行额外检查:由于badfoo是成员函数,因此它尝试构造有效的隐式成员引用表达式。然后它捕获了一个事实,即从静态函数隐式调用成员函数并检测上下文不匹配。此诊断完全由编译器决定(在实例化的情况下不会)。

答案 3 :(得分:2)

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

由于Derived类型尚未在代码中的任何位置实例化,因此编译器不会发出任何与之相关的代码。特别是,badbar将被忽略。

现在,当您声明Derived的实例(例如Derived&lt; int&gt;)但不使用其任何成员时,编译器将只创建一个包含已使用的成员的类型,并省略其他成员。仍然没有关于badbar的错误。

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

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

  

除非已明确实例化或明确专门化类模板或成员模板的成员,否则在需要成员定义存在的上下文中引用特化时,将隐式实例化成员的特化。

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

修改

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

  

我们分析哪些未经实例化的模板?出于性能原因,我不认为我们应该分析所有未实例化的模板,因为我们可能会发现自己反复分析了Boost和STL等的大部分内容。

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

答案 4 :(得分:1)

首先,如果您要实例化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
                ~~~~~~~~~~~~ ^

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