在三元expression中与逗号混淆
今天我发现了以下有趣的代码:
SomeFunction(some_bool_variable ? 12.f, 50.f : 50.f, 12.f)
我创build了一个小样本来重现行为:
class Vector3f { public: Vector3f(float val) { std::cout << "vector constructor: " << val << '\n'; } }; void SetSize(Vector3f v) { std::cout << "SetSize single param\n"; } void SetSize(float w, float h, float d=0) { std::cout << "SetSize multi param: " << w << ", " << h << ", " << d << '\n'; } int main() { SetSize(true ? 12.f, 50.f : 50.f, 12.f); SetSize(false ? 12.f, 50.f : 50.f, 12.f); }
( 现场示例 )
我运行上面的代码得到的结果是:
clang++ -std=c++14 -O2 -Wall -pedantic -lboost_system -lboost_filesystem -pthread main.cpp && ./a.out main.cpp:29:20: warning: expression result unused [-Wunused-value] SetSize(true ? 12.f, 50.f : 50.f, 12.f); ^~~~ main.cpp:30:21: warning: expression result unused [-Wunused-value] SetSize(false ? 12.f, 50.f : 50.f, 12.f); ^~~~ 2 warnings generated. SetSize multi param: 50, 12, 0 SetSize multi param: 50, 12, 0
我期望在这两种情况下,一个参数将被传递给SetSize(float)
。 然而,两个参数被传递,我觉得非常困惑(特别是因为三元优先于逗号;所以我认为逗号不是在这种情况下定界函数的参数)。 例如,如果使用true
,那么三元应该导致12.f, 50.f
。 在这个expression式中,逗号左边的值被丢弃/忽略,所以我希望最终的结果是:
SetSize(50.f);
第二部分的困惑是,无论我们在三元组中使用true
还是false
,相同的2个值被传递给函数。 true
情况应该是h=12, w=50
我想…
我看到编译器试图提醒我一些事情,但是我不太明白发生了什么。 有人可以分解这个逻辑,并一步一步地解释结果吗?
三元运算符的第二部分是自包含的,而第三部分则不是。 语法如下:
条件expression式 :
逻辑或expression
逻辑或expression式? expression式:赋值expression式
所以你的函数调用是这样的:
SetSize((true ? (12.f, 50.f): 50.f), 12.f)
那三元的expression是true ? (12.f, 50.f): 50.f
true ? (12.f, 50.f): 50.f
被评估为函数的第一个参数。 然后12.f
作为第二个值传递。 这种情况下的逗号不是逗号运算符,而是函数参数分隔符。
从C ++标准的第5.18节:
2在逗号赋予特殊意义的上下文中,[ 例子:在函数参数列表(5.2.2)和初始值设定项列表(8.5) – 例子结尾 ]中,第5章中描述的逗号运算符只能出现在括号内。 [ 例如:
f(a, (t=3, t+2), c);
有三个论点,其中第二个是价值5。 – 结束示例 ]
如果您希望将最后两个子expression式分组在一起,则需要添加括号:
SetSize(true ? 12.f, 50.f : (50.f, 12.f)); SetSize(false ? 12.f, 50.f : (50.f, 12.f));
现在你有一个逗号运算符, SetSize
的单参数版本被调用。
这是因为C ++不把第二个逗号当作逗号操作符 :
逗号分隔的列表中的逗号(例如函数参数列表
f(a, b, c)
和初始值设定项列表int a[] = {1,2,3}
)不是逗号运算符。
就第一个逗号而言,C ++没有别的select,只能把它当作一个逗号操作符。 否则,parsing将是无效的。
查看它的一个简单的方法是认为只要C ++分析器发现?
在允许逗号分隔符的上下文中,它会查找匹配项:
完成expression式的第一部分,然后尽可能less地匹配以完成第二个expression式。 即使删除了两个参数的重载,第二个逗号也不会被视为操作符。
编译器警告你,你正在扔掉你的浮点文字的50%。
让我们分解它。
// void SetSize(float w, float h, float d=0) SetSize(true ? 12.f, 50.f : 50.f, 12.f); // ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^
在这里,我们提出一个使用条件运算符作为第一个参数,文字12.f
作为第二个参数的expression式; 第三个参数保留默认值( 0
)。
对真的。
它是这样parsing的(因为没有其他有效的parsing方法):
SetSize( (true ? 12.f, 50.f : 50.f), 12.f); // ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^
第二个参数的价值是直截了当的,所以让我们来看看第一个:
true ? 12.f, 50.f : 50.f
意即:
- 如果是,则结果是
12.f, 50.f
- 否则,结果是
50.f
那么,真的是永远是真的,所以我们可以立即打折第二个选项。
expression式12.f, 50.f
使用了逗号运算符 ,它对两个操作数进行求值,然后将第一个操作数和第二个操作数进行比较,即50.f
因此,整个事情其实是:
SetSize(50.f, 12.f);
如果这不是一些神秘而毫无意义的编程“难题”,那么这是一段非常愚蠢的代码,一个没有受过教育的程序员希望将expression式“解压”成更加等价的东西:
SetSize( (true ? 12.f : 50.f), (true ? 50.f : 12.f) );
…这仍然是可怕的和无用的代码,因为真实仍然是真实的。
(显然,在写入false
的情况下,这些值是不同的,但应用相同的逻辑。)
真正的情况应该是h = 12,w = 50我想…
它是。 这就是你发布的输出说的。 当你不擅自重新安排参数时,它是更清楚的,即它们是w = 50 h = 12。