为什么标准的C++语法没有涵盖这种情况?

7
我主要是指C++03标准,但经过快速浏览,它也适用于C++11标准。
以下代码在VC++2010中成功编译并执行:
template<typename T> 
class CC { 
  public: 
    T f(T a) { 
            return a*a;
    } 
};
template<> 
class ::CC<int> {  //<--- ::CC<int> syntax allowed by VC++2010, but is it non-standard ?
  public: 
    int f(int a) { 
            return a*a;
    } 
};

int main(int argc, _TCHAR* argv[])
{
    ::CC<int> c;
}

请注意使用::CC<int>语法来引用全局命名空间中定义的模板。这与NamespaceA::CC<int>语法不同,其中::运算符前面有一些内容。我尝试使用C++03标准中的语法解析此代码,但出现了错误,似乎标准只接受NamespaceA::CC<int>形式在类头声明中使用。
仔细观察后发现,问题在于标准中通过以下语法定义了class-head
class-head:
   class-key identifier(optional) base-clause(optional)
   class-key nested-name-specifier identifier base-clause(optional)
   class-key nested-name-specifier(optional) template-id base-clause(optional)

由于nested-name-specifier的形式为AA::bb::,所以它不接受我的::CC。 我的问题是,为什么C++标准不允许使用::CC的形式?这只是我对标准语法的错误解释吗?正确的语法应该是这样的:

class-head:
   ...
   class-key '::'(optional) nested-name-specifier(optional) template-id base-clause(optional)

请注意,上述表单实际上被标准用于其他地方,例如在指定声明符id时使用:
declarator-id:
   id-expression
   ::(optional) nested-name-specifier(optional) class-name

当然,嵌套名称指示符可以是::,而CC是标识符,...? - Columbo
2
参见:https://dev59.com/Q03Sa4cB1Zd3GeqPwqHE - rubenvb
我注意到C++11在嵌套名称限定符之前添加了一个前导'::',而c++03标准要求在'::'之前有类或命名空间名称。可能是C++03中的一个“错误”。 - JavaMan
@JavaMan,另一个问题中的链接(搜索“355。”,#无效)似乎确实表明了这一点。不清楚它是否在C++11或14中修复,甚至还没有。 - rubenvb
2
@Columbo http://wg21.link/cwg1411 - T.C.
@JavaMan -- c++11没有添加一个裸的 :: 作为 nested-name-specifier。该语言中长期存在的错误(首次在2002年被识别)直到C++14才被修补。 - David Hammen
2个回答

3

以下是Columbo的评论:

当然,嵌套名称指定符可以是::,而CC是标识符,...?

至少在这个问题的上下文中不是这样。直到C++标准的2014年版本之前,一个裸的双分号不被视为嵌套名称指定符。标准的2003年版本规定,嵌套名称指定符有两种形式,BNF表示如下:

  • 类名或命名空间名 :: 嵌套名称指定符opt
  • 类名或命名空间名 :: template 嵌套名称指定符

在此规范中没有空间可以容纳裸的class ::CC。2011年的版本增加了很多关于嵌套名称指定符的BNF:

  • ::opt 类型名称 ::
  • ::opt 命名空间名称 ::
  • decltype说明符 ::
  • 嵌套名称指定符 标识符 ::
  • 嵌套名称指定符 templateopt 简单模板ID ::

这仍然没有空间可以容纳class ::CC。标准的2014年版本最终解决了这个问题,规定嵌套名称指定符是以下之一:

  • ::
  • 类型名称 ::
  • 命名空间名称 ::
  • decltype说明符 ::
  • 嵌套名称指定符 标识符 ::
  • 嵌套名称指定符 templateopt 简单模板ID ::


有很多方法来看待这个有趣的“特性”。其中一个是这是语言规范中长期存在的错误,在2002年首次被确定为问题#355。编译器供应商的工作之一是识别和修补语言规范中的错误,然后在即将发布的标准版本中修复这些错误。从这个角度来看,template<> class ::CC<int> {...}应该编译。

另一个观点是这不是一个 bug。2003 年和 2011 年标准中的 nested-name-specifier 的 BNF 非常明确,因此 template<> class ::CC<int> {...} 不应该编译。无论这是一个不幸的缺陷还是一个故意的特性并不重要。从这个角度来看,问题中的代码不应该编译。
哪种观点是正确的是有争议的。最初报告这个差异的问题没有被拒绝的事实表明这个报告有一定的价值。另一方面,标准的两次修订中都没有对此进行任何处理也说明了一些问题。
话虽如此,现在标准已经澄清,新版本的 GCC 中存在一个 bug,即使指定 --std=c++14,它们也不允许 template<> class ::CC<int> {...} 编译。

2
在C++草案中,nested-name-specifier[class].11中提到:

如果类头名包含嵌套的名称限定符,则类说明符应引用先前直接在嵌套名称限定符所引用的类或命名空间中声明的类,或者在该命名空间的内联命名空间集合(即不仅仅是通过using声明继承或引入),并且类说明符应出现在封闭先前声明的命名空间的命名空间中。 在这种情况下,定义的类头名称的嵌套名称限定符不得以decltype-specifier开头。

而根据[expr.prim.id.qual],它当然也可以是::
在您的代码中,您在模板类特化中使用了class ::CC<int>,其中[temp.expl.spec].2也适用:
一个显式特化应该在封闭特化模板的命名空间中声明。如果声明符号或类头名称没有限定符,则应在最近的包围模板的命名空间中声明,或者如果该命名空间是内联的([namespace.def]),则可以在其包围命名空间集中的任何命名空间中声明。这样的声明也可以是定义。如果声明不是定义,则可以稍后定义特化([namespace.memdef])。
因此,我认为使用限定名称应该没问题。

然而,GCC在编译此代码时出现错误,尽管Clang可以优雅地处理它。看起来是一个GCC的错误。 - rubenvb
@rubenvb 是的,我也注意到了,在GCC 4.9和5.3.0以及Clang 3.7.1中。 - jotik
这个答案涉及到2014年版本的标准,但不适用于2011年及之前的版本。问题#355在2011年3月作为N3259文件被投票纳入WP。这是在N3242文件已经被批准为C ++ 20011标准草案之后进行的。 - David Hammen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接