例如,转发声明和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上,但它仍然合法。
答案 0 :(得分:4)
声明“这不是合法的C ++”并不十分准确。在函数声明出现的范围内定义lhs
和rhs
是合法的C ++。参数lhs
和rhs
不在外部范围内,但是名称可能在其他声明的范围内;更改函数返回类型的范围可以默默地更改以前有效程序的含义。
在C ++中,作用域并不总是处于词汇顺序。例如,在成员函数体内部,所有成员都是可见的,甚至是尚未声明的成员。 (前导返回类型也不是成员函数体的一部分,但是尾部返回是。)
语法中是否存在一些奇怪的极端情况,例如,除非您知道其中的标识符是Type还是变量,否则无法找到decltype非终端生成的结尾?
我不这么认为;围绕decltype
的论证的括号应该是明确的。但是,如果您不知道标识符是否是模板,则可能会以非常微妙的方式影响<
,>
和>>
标记的含义。并且不知道标识符是类型还是函数也会在解析表达式时产生困难(即使在C中)。所以肯定存在一个角落案例,其中名称的“种类”很重要。
答案 1 :(得分:4)
这不是容量问题,而是向后兼容问题。
该语言的新版本必须保留几乎所有旧功能以保持向后兼容性。在原始C ++中,未在类或函数的范围内查找成员函数返回类型。它被查询在函数声明的封闭范围内。对于向后兼容性,更改此规则可能是灾难性的。
唯一的解决方法是专门为decltype
参数设置一个例外。但这会非常不一致且容易出错。
答案 2 :(得分:1)
他们必须在某处“内部”和“外部”范围之间画线。正如您所提到的,当可以解析其中的标识符时,更容易找到生产的结尾。它在编译器中更容易实现,并且在语言标准中更容易一致地指定。这可能是第一个编译器以这种方式实现的原因,以及为什么语言总是需要typename
和template
关键字来解决模板扩展中由未定义(嵌套)标识符引起的语法歧义。
标识符在出现在声明中之前永远不会被使用,这是帮助人类或机器读者找到相关声明的一个很好的规则。 (除了类成员之外,应用特殊的两遍解析来提高便利性。)
一旦完成,就会做出承诺并且语言不会改变。除了添加一个特性之外,就是这就是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)
你的前提是前向声明纯粹是为了节省内存以允许流解析(而不是将整个源文件保存在内存中)并不是真的正确。
我们使用的许多解析算法都是基于正向传递,如果你必须回溯,它可以在解析时间(或空间)中创建一个指数级的爆炸来消除歧义。
例如 - 在声明标识符之前暂时接受它而不知道它是什么意思 - 意味着你必须暂时解析它的所有可能性。 (包括但不限于拼写错误)