在回答关于 SO 的另一个问题时,我发现 gcc 出现了一个有点可疑的编译器错误。有问题的片段是
template <class T> class A;
template <class T, class U>
void operator*(A<T>, A<U>);
template <class T>
class A {
friend void ::operator*(A<T>, A<T>);
...
最后一行给出了著名的警告
朋友声明“
void operator*(A<T>, A<T>)
”声明了一个非模板函数
导致以后出现硬错误。完整的代码可以在这里找到。
现在,问题是我认为这种行为不合适。[temp.friend]/1 中的标准说:
对于不是模板声明的友元函数声明:
— 如果朋友的名字是一个合格的或不合格的模板ID,朋友声明是指一个函数模板的特化,否则
— 如果朋友的名字是一个qualified-id,并且在指定的类或命名空间中找到了一个匹配的非模板函数,则朋友声明引用该函数,否则,
— 如果朋友的名字是一个qualified-id,并且在指定的类或命名空间中找到了一个模板函数的匹配特化 ,则朋友声明引用该函数特化,否则,
这是 C++03;C++11 包含类似的子句
模板的特化由 [temp.spec]/4 定义:
...特化是实例化或显式特化的类、函数或类成员(14.7.3)。
和 [temp.fct.spec]/1:
从函数模板实例化的函数称为函数模板特化;函数模板的显式特化也是如此。模板参数可以显式指定...
[temp.arg.explicit]/2 说明了为函数规范指定模板参数列表:
引用函数模板的特化时,可以指定模板参数列表
...
——在朋友声明中。
可以从显式模板参数列表中省略可以推导出的尾随模板参数(14.8.2)。如果所有的模板参数都可以推导出来,那么它们都可以被省略;在这种情况下,空模板参数列表 <> 本身也可以省略。
所以,[temp.fct.spec]/1,::operator*<T,T>(A<T>, A<T>)
是一个函数模板特化;并且由于可以推导出模板参数,因此可以将其称为::operator*(A<T>, A<T>)
。所以我得出结论,朋友声明中的限定 ID 表示函数模板特化。
我认为强调的条件得到满足;因此,friend 声明应该与具有操作符模板(隐式)特化的类友好。然而,gcc 不这么认为并继续第四个项目符号,它只涉及由 unqualified-ids 指定的朋友,即使该朋友实际上是由一个qualified-id 命名的。
在这种情况下,我的解释是正确的还是 gcc 正确?