在三元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。