与三元表达中的逗号混淆

时间:2017-05-31 23:05:50

标签: c++

我今天发现了以下有趣的代码:

SomeFunction(some_bool_variable ? 12.f, 50.f : 50.f, 12.f)

我创建了一个小样本来重现行为:

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);
}

Live Sample

运行上面代码得到的结果是:

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。在这个表达式中,逗号左边的值被删除/忽略,所以我希望最终结果是:

SetSize(50.f);

混淆的第二部分是我们是否在三元组中使用truefalse,将相同的2个值传递给函数。 true案例应为h=12, w=50我认为......

我看到编译器试图警告我某些事情,但我不太明白发生了什么。有人可以分解这个逻辑并逐步解释结果吗?

3 个答案:

答案 0 :(得分:30)

虽然三元运算符的第二部分是自包含的,但第三部分却不是。语法如下:

  

条件表达式

     
    

逻辑或表达

         

logical-or-expression?表达式:赋值表达式

  

所以你的函数调用实际上是这样的:

SetSize((true ? (12.f, 50.f): 50.f), 12.f)

因此三元表达式true ? (12.f, 50.f): 50.f被评估为函数的第一个参数。然后12.f作为第二个值传递。在这种情况下,逗号是不是逗号运算符,而是函数参数分隔符。

来自C++ standard的第5.18节:

  

2 在逗号被赋予特殊含义的上下文中,[   示例:在函数参数列表(5.2.2)和初始值列表(8.5) - 结束示例]中描述的逗号运算符   在第5条中只能出现在括号中。 [   示例:

f(a, (t=3, t+2), c);
     

有三个参数,第二个参数的值为5。 - 结束   例子]

如果要将最后两个子表达式组合在一起,则需要添加括号:

SetSize(true ? 12.f, 50.f : (50.f, 12.f));
SetSize(false ? 12.f, 50.f : (50.f, 12.f));

现在你有一个逗号运算符,SetSize的单个参数版本被调用。

答案 1 :(得分:17)

这是因为C++ does not treat the second comma as a comma operator

  

各种以逗号分隔的列表中的逗号,例如函数参数列表f(a, b, c)和初始化列表int a[] = {1,2,3},不是逗号运算符。

就第一个逗号而言,C ++没有其他选择,只能将其视为逗号运算符。否则,解析将无效。

查看它的一种简单方法是认为只要C ++解析器在允许逗号分隔符的上下文中找到?,它就会查找匹配的:来完成表达式的第一部分,然后匹配尽可能少的完成第二个表达式。即使您删除了两个参数的重载,第二个逗号也不会被视为运算符。

答案 2 :(得分:10)

编译器警告你,你正在丢弃50%的浮点文字。

让我们分解它。

// void SetSize(float w, float h, float d=0)
SetSize(true ? 12.f, 50.f : 50.f, 12.f);
//      ^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^

这里我们提出一个表达式,使用条件运算符作为第一个参数,文字12.f作为第二个参数;第三个参数保留默认值(0)。

是的,真的。

它被解析为这样(因为没有其他有效的方法来解析它):

SetSize( (true ? 12.f, 50.f : 50.f), 12.f);
//        ^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^

第二个参数的值很简单,所以让我们检查第一个参数:

true ? 12.f, 50.f : 50.f

这意味着:

  • 如果为true,则结果为12.f, 50.f
  • 否则,结果为50.f

嗯,真的总是如此,所以我们可以立即打折第二个选项。

表达式12.f, 50.f使用逗号运算符,它会对两个操作数进行计算,然后将第一个操作数丢去,然后产生第二个操作数,即50.f

因此,整个事实上是:

SetSize(50.f, 12.f);

如果这不是一些神秘而毫无意义的编程“谜题”,那么这是一段非常愚蠢的代码,一位未受过教育的程序员希望将表达式“解包”成更相当于:

SetSize(
   (true ? 12.f : 50.f),
   (true ? 50.f : 12.f)
);

... 仍然是可怕且无用的代码,因为true仍然是真的。

(显然,在编写false的情况下,值不同,但适用相同的逻辑。)

  

真实案例应为h = 12,w = 50我认为......

是的。这就是你发布的输出结果。当你不随意重新安排论证时,它会更清楚,即它们是w = 50 h = 12。