为什么C ++ 11仍然强制执行词汇排序以实现可见性

时间:2013-01-17 21:23:52

标签: c++ c++11 language-design

例如,转发声明和Wikipedia section on Alternative function syntax

  

类型Ret是Lhs和Rhs将产生的类型的添加。即使使用[...] decltype,这也是不可能的:

template<class Lhs, class Rhs>
decltype(lhs+rhs)  //Not legal C++11
      adding_func(const Lhs &lhs, const Rhs &rhs) {
  return lhs + rhs;
}
     

这不是合法的C ++,因为还没有定义lhs和rhs;在解析器解析了函数原型的其余部分之前,它们将不是有效的标识符。

当系统处于主要内存压力之下时,这是可以理解的(它允许传递作为流操作完成)。但是,为什么在这个时代(我的智能手机有足够的RAM来保存完整的源代码,完整的解析树,然后是一些合理的文件),令牌的顺序是否重要?语法中是否存在一些奇怪的极端情况,例如,除非您知道其中的标识符是Type还是变量,否则无法找到decltype非终端生成的结尾?另外,为什么全局/命名空间作用域声明的顺序根本不重要?


编辑,结果证明这是合法的:

class foo {};
foo Foober(int foo) { return ::foo(); }

如果我在野外遇到这种情况,它最终会出现在The Daily WTF上,但它仍然合法。

4 个答案:

答案 0 :(得分:4)

声明“这不是合法的C ++”并不十分准确。在函数声明出现的范围内定义lhsrhs是合法的C ++。参数lhsrhs不在外部范围内,但是名称可能在其他声明的范围内;更改函数返回类型的范围可以默默地更改以前有效程序的含义。

在C ++中,作用域并不总是处于词汇顺序。例如,在成员函数体内部,所有成员都是可见的,甚至是尚未声明的成员。 (前导返回类型也不是成员函数体的一部分,但是尾部返回是。)

  

语法中是否存在一些奇怪的极端情况,例如,除非您知道其中的标识符是Type还是变量,否则无法找到decltype非终端生成的结尾?

我不这么认为;围绕decltype的论证的括号应该是明确的。但是,如果您不知道标识符是否是模板,则可能会以非常微妙的方式影响<>>>标记的含义。并且不知道标识符是类型还是函数也会在解析表达式时产生困难(即使在C中)。所以肯定存在一个角落案例,其中名称的“种类”很重要。

答案 1 :(得分:4)

这不是容量问题,而是向后兼容问题。

该语言的新版本必须保留几乎所有旧功能以保持向后兼容性。在原始C ++中,未在类或函数的范围内查找成员函数返回类型。它被查询在函数声明的封闭范围内。对于向后兼容性,更改此规则可能是灾难性的。

唯一的解决方法是专门为decltype参数设置一个例外。但这会非常不一致且容易出错。

答案 2 :(得分:1)

他们必须在某处“内部”和“外部”范围之间画线。正如您所提到的,当可以解析其中的标识符时,更容易找到生产的结尾。它在编译器中更容易实现,并且在语言标准中更容易一致地指定。这可能是第一个编译器以这种方式实现的原因,以及为什么语言总是需要typenametemplate关键字来解决模板扩展中由未定义(嵌套)标识符引起的语法歧义。

标识符在出现在声明中之前永远不会被使用,这是帮助人类或机器读者找到相关声明的一个很好的规则。 (除了类成员之外,应用特殊的两遍解析来提高便利性。)

一旦完成,就会做出承诺并且语言不会改变。除了添加一个特性之外,就是这就是C ++ 11解决问题的方法。

template<class Lhs, class Rhs>
  auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs)
    {return lhs + rhs;}

顺便说一下,他们没有提到另一种解决方法:

template<class Lhs, class Rhs>
  decltype( declval< Lhs >() + declval< Rhs >() )
    adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;}

答案 3 :(得分:1)

你的前提是前向声明纯粹是为了节省内存以允许流解析(而不是将整个源文件保存在内存中)并不是真的正确。

我们使用的许多解析算法都是基于正向传递,如果你必须回溯,它可以在解析时间(或空间)中创建一个指数级的爆炸来消除歧义。

例如 - 在声明标识符之前暂时接受它而不知道它是什么意思 - 意味着你必须暂时解析它的所有可能性。 (包括但不限于拼写错误)