我可以这样做:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (rand() % num < 7) Boo(8);
}
};
Boo(8);
return 0;
}
这样可以编译,我的反结果是 21 。但是,当我尝试创建传递构造函数参数而不是整数文字的Boo
对象时,出现编译错误:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (rand() % num < 7) Boo(num); // No default constructor
// exists for Boo
}
};
Boo(8);
return 0;
}
在第二个示例中如何调用默认构造函数,但在第一个示例中如何调用呢?这是我在Visual Studio 2017上遇到的错误。
在在线C ++编译器onlineGDB上,我得到以下错误:
error: no matching function for call to ‘main()::Boo::Boo()’
if (rand() % num < 7) Boo(num);
^
note: candidate expects 1 argument, 0 provided
答案 0 :(得分:87)
Clang发出以下警告消息:
<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
Boo(num); // No default constructor
^~~~~
这是最令人头疼的解析问题。因为Boo
是类类型的名称,而num
不是类型名称,所以Boo(num);
可以是{{1}的临时类型Boo
的构造。 }作为num
的构造函数的参数,也可以是声明Boo
,并在声明符Boo num;
周围带有额外的括号(声明符可能总是有)。如果两者都是有效的解释,则该标准要求编译器假设一个声明。
如果将其解析为声明,则num
将调用默认构造函数(不带参数的构造函数),该构造函数既不是由您声明的,也不是隐式声明的(因为您声明了另一个构造函数)。因此该程序格式不正确。
Boo num;
并不是问题,因为Boo(8);
不能是变量的标识符(declarator-id),因此它被解析为使用{{创建临时8
的调用1}}作为构造函数的参数,因此不会调用默认的构造函数(未声明),而是调用您手动定义的构造函数。
您可以通过将临时变量设为命名变量,例如使用Boo
而不是8
(因为不允许在声明符周围使用Boo{num};
)来消除声明中的歧义。 Boo(num);
,或将其作为操作数放在另一个表达式中,例如{}
,Boo temp(num);
等
请注意,如果默认构造函数可用,则声明的格式将正确,因为声明位于(Boo(num));
的分支块范围内,而不是函数的块范围内,并且只会遮盖(void)Boo(num);
在函数的参数列表中。
无论如何,将临时对象创建误用于应该是正常(成员)函数调用的东西似乎不是一个好主意。
仅在意图中创建一个临时类型并立即将其丢弃,或者如果意图是创建一个直接用作临时对象的临时类型,则可能发生这种特殊类型的最令人烦恼的分析,并在括号中使用单个非类型名称。初始化程序,例如if
(实际上声明函数num
的类型为Boo boo(Boo(num));
的参数名为boo
并返回num
)。
通常不打算立即丢弃临时变量,并且可以使用大括号初始化或双括号(Boo
,Boo
或Boo boo{Boo(num)}
而不是{{1} }。
如果Boo boo(Boo{num})
不是类型名称,则它不能是声明,也不会出现问题。
我还要强调一点,Boo boo((Boo(num)));
正在创建类型为Boo boo(Boo((num)));
的新临时文件,即使在类范围和构造函数定义中也是如此。就像人们可能误认为的那样,它不是像通常的非静态成员函数那样使用调用者的Boo
指针对构造函数的调用。无法在构造函数主体内以这种方式调用另一个构造函数。这只能在构造函数的成员初始化器列表中。
即使由于[stmt.ambig]/3而由于缺少构造函数而导致声明不正确的情况,也会发生这种情况:
消歧纯粹是句法上的;也就是说, 在这样的陈述中出现的名字,除了它们是否 是否输入类型名称,通常不会在其中使用或更改 消除歧义。
[...]
歧义消除是在解析之前进行的,并且被声明为声明歧义的语句可能是格式错误的声明。
已在编辑中修复:我忽略了所涉及的声明与函数参数的作用域不同,因此如果构造函数可用,则声明的格式正确。无论如何,在进行歧义消除时都不会考虑这一点。还扩展了一些细节。
答案 1 :(得分:33)
这被称为 最令人烦恼的解析 (The term was used by Scott Meyers in Effective STL)。
Boo(num)
不会调用构造函数,也不会创建临时构造函数。 Clang发出了一个很好的警告(即使使用正确的名称W vexing-parse ):
<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
所以编译器看到的等同于
Boo num;
这是一个可变的脱位。您声明了一个名称为num的Boo变量,即使您要创建一个临时的Boo对象,也需要默认构造函数。 C ++标准要求您的情况下的编译器假定这是一个变量声明。您现在可能会说:“嘿,num是一个整数,请不要那样做。”但是,standard says:
消歧纯粹是句法上的;也就是说,在这种陈述中出现的名称的含义,除了它们是否为类型名称之外,通常不会在歧义中使用或更改。 根据需要实例化类模板,以确定限定名称是否为类型名称。 歧义消除在解析之前进行,并且歧义为声明的声明可能是格式错误的声明。 如果在解析过程中,模板参数中的名称绑定与试用解析中的绑定不同,则程序格式错误。 无需诊断。 [注意:仅当在声明中更早声明名称时,才会发生这种情况。 —注 ]
所以没有办法。
对于Boo(8)
,这不会发生,因为解析器可以确定这不是declecle(8不是有效的标识符名称),并调用构造函数Boo(int)
。
顺便说一句:您可以使用括号将其消除歧义:
if (rand() % num < 7) (Boo(num));
或者我认为更好,请使用新的统一初始化语法
if (rand() % num < 7) Boo{num};
答案 2 :(得分:1)
这是叮当警告
truct_init.cpp:11:11:错误:使用不同类型重新定义了'num':'Boo' vs'int'