我对值初始化的尝试被解释为函数声明,为什么不使用a(());?解决这个问题?


158

Stack Overflow教给我的很多东西是所谓的“最令人烦恼的解析”,经典地用诸如

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

虽然在大多数情况下直观上似乎是a类型对象的声明A,以一个临时B对象作为构造函数参数,但实际上它是一个函数的声明,a返回a A,并带一个指向该函数的指针,该指针返回B而本身不带任何参数。同样的线

A a(); //declares a function

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

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

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

A a(()); //compile error

我的问题是,为什么?是的,我非常清楚正确的“解决方法”是将其更改为A a;,但是我很好奇()在第一个示例中该编译器的额外功能是什么,然后在重新应用该功能时不起作用第二个例子。A a((B()));解决方法是否已将特定例外写入标准?


20
(B())只是一个C ++表达式,仅此而已。这不是任何例外。它唯一的区别是不可能将它解析为类型,因此不是。
Pavel Minaev 09年

12
还应当指出的是第二种情况,A a();不是同一类的。对于编译器,从来没有任何其他方法可以解析它:那个地方的初始化器永远不会包含空括号,因此它始终是函数声明。
Johannes Schaub-litb

11
litb的优点是一个微妙而又重要的观点,值得强调-该声明'A a(B())'中存在歧义的原因在于'B()'的解析中->它既可以是表达式又可以是&声明,并且编译器必须在expr上“选择” decl-因此,如果B()是decl,则“ a”只能是func decl(而不是变量decl)。如果允许'()'作为初始值设定项'A a()'将是模棱两可的-但expr vs decl不能,但是var decl vs func decl-没有规则优先于另一个decl-因此'()此处不允许将'用作初始化程序-并且不会产生歧义。
Faisal Vali

6
A a();不是最令人讨厌的解析的例子。这是一个简单的函数声明,就好像它是在C.
皮特贝克尔

2
“正确的'解决方法'是将其更改为A a;”是错误的。这不会给您初始化POD类型。要获得初始化,请写信A a{};
干杯和健康。-Alf

Answers:


70

没有开明的答案,仅是因为C ++语言没有将其定义为有效语法。

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

 ((0));//compiles

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

要了解有关如何定义语言以及编译器如何工作的更多信息,您应该了解形式语言理论或更具体地讲上下文无关文法(CFG)和相关材料,例如有限状态机。如果您对此感兴趣,尽管Wikipedia页面还不够,那么您必须获得一本书。


45
更简单地说:因为(x)是有效的C ++表达式,而()不是。
Pavel Minaev 09年

我接受了这个答案,此外,帕维尔(Pavel)对我的最初问题的评论对我有很大帮助
-GRB


29

C函数声明符

首先,有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 ++希望在这里使用标准表达式。在C ++中,您可以编写以下代码。

int value = int();

和下面的代码。

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

C ++期望括号内的表达式是...表达式,而不是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 ++尝试转换charconst char *。无论哪种方式,都只用type的一个值调用构造函数char。没有一个类型为的参数的重载char,因此编译器感到困惑。您可能会问-为什么参数为char类型?

(100, '.')

是的,,这里是一个逗号运算符。逗号运算符采用两个参数,并给出右侧参数。它并不是真正有用,但是我的解释值得我们了解。

相反,要解决最烦人的解析,需要以下代码。

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

参数在括号中,而不是整个表达式。实际上,只需在括号中添加一个表达式即可,因为使用C ++功能足以打破C语法就足够了。事情使我们到了零争论的地步。

构造函数中的零参数

您可能已经eighty_four在我的解释中注意到了该功能。

int eighty_four();

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

int eighty_four(());

为什么呢?好吧,()不是表达。在C ++中,必须将表达式放在括号之间。您不能用auto value = ()C ++ 编写代码,因为()它没有任何意义(即使那样,如空元组(请参阅Python),它也将是一个参数,而不是零)。实际上,这意味着您不使用C ++ 11 {}语法就不能使用速记语法,因为括号中没有表达式,并且函数声明的C语法将始终适用。


12

你可以代替

A a(());

A a=A();

32
“更好的解决方法”并不等效。int a = int();初始化a为0,int a;a初始化。正确的解决方法是A a = {};A a;在默认初始化完成您想要的操作时以及A a = A();在所有其他情况下-或者只是A a = A();连续使用,请使用聚合。在C ++ 11中,只需使用A a {};
Richard Smith

6

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

语法进一步将a定义assignment-expression为其他几种类型的表达之一,其中任何一种都不可以是空(或只能是空格)。

因此,您之所以不能拥有A a(())它,仅仅是因为语法不允许这样做。但是,我无法回答为什么创建C ++的人不允许将空括号的这种特殊用法用作某种特殊情况-我想如果有的话,他们宁愿不使用这种特殊情况合理的选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.