【问题标题】:Possible gcc bug while befriending template specialization与模板专业化交朋友时可能出现 gcc 错误
【发布时间】:2012-11-11 08:58:12
【问题描述】:

在回答关于 SO 的另一个问题时,我遇到了一个有点可疑的 gcc 编译器错误。有问题的sn-p是

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&lt;T&gt;, A&lt;T&gt;)'声明一个 非模板函数

导致以后出现硬错误。完整代码可以在here找到。

现在,问题是我认为这种行为不合适。 [temp.friend]/1 中的标准说:

对于不是模板声明的友元函数声明:

——如果朋友的名字是合格或不合格的模板ID,朋友声明是指一个函数模板的特化,否则

——如果朋友的名字是一个限定ID,并且在指定的 类或命名空间,友元声明引用该函数,否则,

——如果朋友的名字是一个限定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*&lt;T,T&gt;(A&lt;T&gt;, A&lt;T&gt;) 是一个函数模板特化;并且由于可以推导出模板参数,所以可以称为::operator*(A&lt;T&gt;, A&lt;T&gt;)。所以我得出结论,朋友声明中的qualified-id表示一个函数模板特化。


我认为强调的条件已经满足;因此,friend 声明应该与具有操作符模板(隐式)特化的类友好。然而,gcc 不这么认为,并继续讨论第四个项目符号,它只涉及由非限定 ID 指定的朋友,即使该朋友实际上是由一个合格 ID 命名的

在这种情况下我的解释是正确的还是 gcc 正确?

【问题讨论】:

  • 我的 GCC 说,“如果这不是您想要的,请确保函数模板已经被声明并在此处的函数名称后面添加 &lt;&gt;”,确实可以使用 operator*&lt;&gt; .
  • @KerrekSB:是的,我知道——你可能已经注意到,ideone gcc 也是这么说的;但是,问题不在于 gcc 所说的内容或存在哪些解决方法,而是 gcc 所说的是否符合标准。
  • 嗯,operator* 在您看来是不是“合格 ID”?我认为该语言指的是 friend void Bar&lt;T&gt;::zip() 之类的东西。
  • @KerrekSB:::operator*,在 sn-p 中使用,确实看起来确实像一个限定 ID。
  • MSVC 需要与msdn.microsoft.com/en-us/library/f1b2td24(v=vs.110).aspx 相同的语法,所以我怀疑这是编译器错误。据我所知,&lt;&gt; 语法等同于friend void ::operator*&lt;T&gt;(A&lt;T&gt;, A&lt;T&gt;);。希望更熟悉该标准的人可以解释原因。

标签: c++ class templates


【解决方案1】:

我相信 gcc 是正确的。

首先是当前的措辞:

如果朋友的名字是一个qualified-id和一个匹配的函数 在指定的类或命名空间中找到模板,朋友 声明 指的是该函数的推导特化 模板 (14.8.2.6),否则

来自 [14.8.2.6 从函数声明中推导出模板参数]:

1 在一个声明中,其 declarator-id 指的是 函数模板,模板实参推导 确定声明所指的专业化。 具体来说,这是为显式实例化(14.7.2)完成的, 显式特化 (14.7.3) 和某些友元声明 (14.5.4)。这也是为了确定是否释放 函数模板特化匹配放置操作符 new (3.7.4.2、5.3.4)。在所有这些情况下,P 是函数的类型 模板被视为潜在匹配,并且 A 是 声明中的函数类型或释放的类型 将匹配放置运算符 new 的函数,如中所述 5.3.4.按照 14.8.2.5 中的说明进行扣除。

2 如果对于这样考虑的函数模板集,要么不匹配,要么 考虑了部分排序后的多个匹配项 (14.5.6.2),扣减失败,在申报情况下,程序 格式不正确。

在您的情况下,执行模板参数推导,因为 declarator-id 不引用专业化。我认为重要的部分是whose declarator-id refers to a specialization 作为发生这种情况的条件。简而言之,您需要 &lt;&gt; 才能发生 14.8.2.6p1 中的第一句话(如果我没看错的话)。

更新 让我们分解一下在这种情况下什么是 declarator-id:

qualified-id:
nested-name-specifier templateopt unqualified-id
:: identifier
:: operator-function-id
:: literal-operator-id
:: template-id

从上面的语法可以看出,void ::operator*(A&lt;T&gt;, A&lt;T&gt;):: operator-function-id不是 :: template-id。这意味着语法永远不能声明模板函数(如错误消息中所述)。要使其成为模板 ID,您必须使用 operator-function-id &lt; template-argument-listopt&gt; 语法。

【讨论】:

  • 我认为你错了;为了证明我不是这样,请阅读我对问题的更新并争论为什么 declarator-id 不引用函数专业化。顺便说一句,您似乎指的是 C++11 标准;在这种情况下,该标准仅要求在 14.5.4 中“找到匹​​配的函数模板”才能应用第三个项目符号(与模板专业化成为朋友)。
  • 要让 declarator-id 引用特化,语法必须是 operator-function-id &lt; template-argument-listopt&gt;(其中 template-argument-list 是可选的)。这是进行演绎的前提。正如您的报价:Trailing template arguments that can be deduced (14.8.2) may be omitted。但是,要在这种情况下进行推理,声明符 id 必须引用一个特化,所以推理永远不会发生
  • 请参考我的更新,如果你查找:: template-id的语法,void ::operator*永远不能在声明中引用模板函数(编译器会一直认为它是非模板) .
猜你喜欢
  • 1970-01-01
  • 2011-12-22
  • 2011-05-26
  • 1970-01-01
  • 2015-07-04
  • 2018-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多