我的值初始化尝试被解释为一个函数声明,为什么 A (()) ; 不解决它?

Stack Overflow 教会了我许多东西,其中之一就是众所周知的“最令人烦恼的解析”,通常用以下代码行进行演示

A a(B()); //declares a function

虽然对大多数人来说,这看起来像是 A类型的对象 a的声明,将一个临时的 B对象作为构造函数参数,但实际上它是返回 A的函数 a的声明,将一个指向返回 B的函数的指针作为返回值,而该函数本身没有任何参数。同样的路线

A a(); //declares a function

也属于同一类别,因为它声明的不是对象,而是一个函数。现在,在第一种情况下,解决这个问题的通常方法是在 B()周围添加一组额外的括号/圆括号,因为编译器会将其解释为对象的声明

A a((B())); //declares an object

但是,在第二种情况下,同样的操作会导致编译错误

A a(()); //compile error

我的问题是,为什么?是的,我非常清楚正确的解决办法是将它改为 A a;,但是我很好奇,在第一个例子中额外的 ()对编译器有什么作用,而在第二个例子中重新应用它时却不起作用。A a((B()));解决方案是写入标准的特定异常吗?

19814 次浏览

没有开明的答案,这只是因为它没有被 C + + 语言定义为有效的语法... ... 所以,根据语言的定义就是这样。

如果你有一个表达式,那么它是有效的。例如:

 ((0));//compiles

更简单的说法是: 因为 (x)是一个有效的 C + + 表达式,而 ()不是。

为了更多地了解语言是如何定义的,以及编译器是如何工作的,您应该了解 形式语言理论或更具体的 上下文无关语法(CFG)以及有限状态机等相关资料。如果你对此感兴趣,尽管维基百科的页面不够,你还是得买本书。

示例中最里面的括号是一个表达式,在 C + + 中,语法将一个 expression定义为一个 assignment-expression或另一个 expression,后面跟一个逗号和另一个 assignment-expression(附录 A.4-语法摘要/表达式)。

该语法进一步将 assignment-expression定义为几种其他类型的表达式之一,其中没有一种表达式可以为无(或者只有空格)。

所以不能使用 A a(())的原因很简单,因为语法不允许使用 A a(())。然而,我不能回答为什么创建 C + + 的人不允许在某种特殊情况下使用空括号——我猜如果有合理的替代方案,他们宁愿不用这种特殊情况。

你可以代替他

A a(());

使用

A a=A();

这个问题的最终解决方案是,如果可以的话,转向 C + 11统一的初始化语法。

A a{};

Http://www.stroustrup.com/c++11faq.html#uniform-init

函数声明器

首先,有一个 C。在 C 语言中,A a()是函数声明。例如,putchar有以下声明。通常,这样的声明存储在头文件中,但是如果您知道函数的声明是什么样子的话,没有什么可以阻止您手动编写它们。参数名称在声明中是可选的,所以我在本例中省略了它。

int putchar(int);

这允许您像下面这样编写代码。

int puts(const char *);
int main() {
puts("Hello, world!");
}

C 语言还允许定义将函数作为参数的函数,其可读性很好的语法看起来像函数调用(当然,它是可读的,只要不返回函数指针)。

#include <stdio.h>


int eighty_four() {
return 84;
}


int output_result(int callback()) {
printf("Returned: %d\n", callback());
return 0;
}


int main() {
return output_result(eighty_four);
}

正如我提到的,C 允许在头文件中省略参数名,因此 output_result在头文件中看起来应该是这样的。

int output_result(int());

构造函数中的一个参数

- 你不认识这个吗?-我提醒你一下。

A a(B());

是的,这是完全相同的函数声明。 Aintaoutput_resultBint

您可以很容易地注意到 C 与 C + + 的新特性之间的冲突。确切地说,构造函数是类名和括号,以及用 ()替代 =的替代声明语法。根据设计,C + + 尝试与 C 代码兼容,因此它必须处理这种情况-即使实际上没有人关心。因此,旧的 C 特性优先于新的 C + + 特性。声明语法尝试将名称作为函数匹配,如果失败,则在返回到 ()的新语法之前进行匹配。

如果其中一个特性不存在,或者有不同的语法(如 C + + 11中的 {}) ,那么对于只有一个参数的语法来说,这个问题就不会发生。

现在你可能会问为什么 A a((B()))可以工作。那么,让我们用无用的括号来声明 output_result

int output_result((int()));

没用的,语法要求变量不在括号里。

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

但是,C + + 在这里需要标准的表达式。

int value = int();

以及下面的代码。

int value = ((((int()))));

C + + 期望括号内的表达式是... well... 表达式,而不是 C 期望的类型。括号在这里没有任何意义。但是,通过插入无用的括号,C 函数声明不匹配,新语法可以正确匹配(它只需要一个表达式,比如 2 + 2)。

构造函数中的更多参数

一个论点当然不错,但两个呢?并不是说构造函数可能只有一个参数。std::string是一个内置的带有两个参数的类

std::string hundred_dots(100, '.');

这一切都很好(从技术上讲,如果它将被写成 std::string wat(int(), char()),那么它将拥有最令人烦恼的解析,但是让我们诚实一点——谁会写那样的东西?但是让我们假设这个代码有一个令人烦恼的问题。你会认为你必须把所有的东西都放在括号里。

std::string hundred_dots((100, '.'));

并非如此。

<stdin>:2:36: error: invalid conversion from ‘char’ to ‘const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of ‘std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
basic_string<_CharT, _Traits, _Alloc>::
^

我不知道为什么 g + + 试图将 char转换成 const char *。不管采用哪种方式,只使用 char类型的一个值调用构造函数。没有一个参数类型为 char的重载,因此编译器会感到困惑。你可能会问——为什么参数是 char 类型的?

(100, '.')

是的,这里的 ,是一个逗号运算符。逗号运算符接受两个参数,并给出右边的参数。这不是真正有用的,但它的东西,我的解释是众所周知的。

相反,要解决最棘手的解析,需要以下代码。

std::string hundred_dots((100), ('.'));

参数在括号中,而不是整个表达式。事实上,只有一个表达式需要用括号括起来,因为使用 C + + 特性稍微打破 C 语法就足够了。事情把我们带到了零争论的地步。

构造函数中的零参数

你可能已经注意到了 eighty_four函数在我的解释。

int eighty_four();

是的,这也受到最令人烦恼的解析的影响。这是一个有效的定义,如果您创建了头文件(而且应该创建头文件) ,那么您很可能已经看到了这个定义。加括号并不能解决问题。

int eighty_four(());

为什么会这样?()不是一种表达方式。在 C + + 中,必须在括号之间放一个表达式。您不能用 C + + 编写 auto value = (),因为 ()没有任何意义(即使有,比如空的 tuple (参见 Python) ,它也只是一个参数,而不是零)。实际上,这意味着如果不使用 C + + 11的 {}语法,就不能使用速记语法,因为没有表达式可以放在括号中,函数声明的 C 语法将始终适用。