例如,以下代码段使用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都拒绝代码,并且他们抱怨的错误是“没有匹配的函数”,而不是在clang中“调用没有对象的非静态成员”。这个问题似乎没有确凿的答案,所以我正在添加一个语言律师标签,正如Daniel Frey建议的那样。
更多更新:
我认为对于问题2,答案现在非常明确:编译器是否添加静态声明会改变诊断。它因编译器和编译器以及同一编译器的不同版本而异。语言律师没有出现,我将接受Daniel Frey的回答,因为它最能解释第一个问题。但是Marco A.和Hadi Brais的答案也值得一读。
答案 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
~~~~~~~~~~~~ ^
编译器不编译未实例化的代码。