编译器检查未经证实的模板代码是什么?
例如,下面的代码片段用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;
}
但是注释掉的行不会被编译。
我的问题是:
为什么badbar()
编译但worsebar()
不会?
如果我将badbar()
更改为静态,则不会进行编译,无论base::badfoo
是否为静态。
标准是否说明了在这种情况下应该检查什么? 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都拒绝代码,他们抱怨的错误是“没有匹配的函数”,而不是“在没有对象的情况下调用非静态成员”。 这个问题似乎没有确凿的答案,所以我按Daniel Frey的建议添加了一个语言律师标签。
更新:
我认为问题2的答案现在非常清楚:编译器是否需要添加静态声明会改变诊断。 它因编译器和编译器以及同一编译器的不同版本而异。 语言律师没有出现,我会接受Daniel Frey的回答,因为它最好地解释了第一个问题。 但是Marco A.和Hadi Brais的回答也值得一读。
标准只需要在第一阶段进行名称查找,当模板首先被解析时。 这在badbar
变成badfoo
,这就是代码编译的原因。 编译器当时不需要执行重载解析。
重载解析(在名称查找之后总是作为一个单独的步骤发生)然后在第二阶段在badbar
被实例化时执行 - 在您的示例中不是这种情况。 这个原则可以在这里找到
3.4名称查询[basic.lookup]
1名称查找规则统一适用于所有名称(包括typedef名称(7.1.3),名称空间名称(7.3)和类名称(9.1)),只要语法允许在特定规则讨论的上下文中使用这些名称。 名称查找将名称的使用与该名称的声明(3.1)相关联。 名称查找应找到一个明确的名称声明(见10.2)。 如果名称查找可能会将多个声明与一个名称关联起来,如果它发现该名称是一个函数名称; 据说这些声明构成了一组重载函数(13.1)。 重载解析(13.3)在名称查找成功后发生。 访问规则(条款11)仅在名称查找和函数重载解析(如果适用)成功后才被考虑。 只有在名称查找后,函数重载解析(如果适用)和访问检查成功,才是表达式处理中进一步使用的名称声明引入的属性(第5章)。
(强调我的)
因此,我会说编译器对于接受代码是正确的,尽管我不确定他们是否需要这样做。
要查看被拒绝的代码,您需要实例化badbar
。
考虑[temp.res] / 8:
如果不能为模板生成有效的特化,并且该模板未实例化,则模板不合格,不需要诊断。
这个(特别是“不需要诊断”位)使得任何编译器的行为都符合worsebar
。 这种代码的实现差异只是QoI问题 - 常见的编译器会做一些分析并且会抱怨。 很难说什么时候完成,并且在升级或切换实施时应该准备回到模板代码。
为了澄清一下,这个版本的代码在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
叮当触发错误,而gcc不会。 这是名称查找成功后的第一种情况,但是clang会执行额外的检查:由于badfoo
是成员函数,因此它会尝试构造有效的隐式成员引用表达式。 然后它捕捉到一个事实,即一个成员函数被隐式地从一个静态函数调用并检测到上下文不匹配。 这个诊断完全取决于编译器(在这种情况下不会出现实例化)。
上一篇: What does a compiler check for uninstantiated template code?
下一篇: Template function overloading with identical signatures, why does this work?