【问题标题】:Can `throw` be inside a comma subexpression within C++ conditional (ternary) operator?`throw` 可以在 C++ 条件(三元)运算符中的逗号子表达式内吗?
【发布时间】:2021-10-17 06:15:38
【问题描述】:

众所周知,throw 可以作为 C++ 三元运算符?: 的第二个或第三个操作数。但它可以在操作数的逗号子表达式中吗?看起来编译器在这方面存在分歧。请考虑一个例子:

#include <iostream>

void foo(bool b) {
    int i = b ? 1 : (throw 0); //ok everywhere
    if ( !b )
        (std::cout << "smth\n", throw 0); //ok everywhere
    i = b ? 2 : (std::cout << "smth\n", throw 0); //ok in MSVC only
};

此示例被 MSVC 接受,但被 GCC 和 Clang 拒绝,演示:https://gcc.godbolt.org/z/6q46j5exP

虽然报错信息:

error: third operand to the conditional operator is of type 'void', but the second operand is neither a throw-expression nor of type 'void'
    7 |     i = b ? 2 : (std::cout << "smth\n", throw 0);
      |             ^

表明它不是故意拒绝的,而是编译器认为第三个操作数不仅具有正式类型void,而且实际上可以返回。

根据https://en.cppreference.com/w/cpp/language/operator_other,看来GCC/Clang是对的

E2 或 E3(但不是两者)是一个(可能带括号的)抛出表达式。

这里我们用括号括起来的逗号表达式以 throw-expression 结尾。

按照标准,MSVC在接受示例的最后一行是不正确的吗?

【问题讨论】:

  • 仅供参考,并没有解释差异尝试i = b ? 2 : (std::cout &lt;&lt; "smth\n", throw 0, 42);这使得子表达式(E2和E3)的类型相同。
  • @RichardCritten,感谢您的想法。这确实被所有人接受:gcc.godbolt.org/z/85YGz9ffG

标签: c++ language-lawyer conditional-operator throw


【解决方案1】:

Clang 和 GCC 拒绝它是正确的。这很简单:

[expr.cond]

2 如果第二个或第三个操作数的类型为void,则应满足以下条件之一:

  • 第二个或第三个操作数(但不是两个)是一个(可能带括号的)throw-expression ([expr.throw]);结果是另一个的类型和值类别。如果操作数是位域,则 conditional-expression 是位域。
  • 第二个和第三个操作数的类型都是void;结果是void 类型并且是prvalue。

这里的措辞非常准确。它说当第一个项目符号适用时,一个操作数是一个抛出表达式。 (std::cout &lt;&lt; "smth\n", throw 0) 不是投掷表达式。它是带括号的逗号表达式。

所以我们只能在第二个子弹的情况下,但它的条件也不成立。所以“应该”的要求被打破了,程序因此是不正确的。

现在,MSVC 可能会为此提供扩展,但它不是标准的。

【讨论】:

    【解决方案2】:

    正如@StoryTeller - Unslander Monica 所指出的,有一个规范限制,操作数的计算结果为void

    但是,我应该注意到,在这种情况下,限制被简单地绕过了:

    #include <iostream>
    
    void foo(bool b) {
        int i = b ? 1 : (throw 0); //ok everywhere
        if ( !b )
            (std::cout << "smth\n", throw 0);
    
        //  OR
    
        i = b ? 2 : throw ((std::cout << "smth\n"), 0);
    
        //  OR
    
        i = b ? 2 : (std::cout << "smth\n", throw 0, 2);
                                                // ^^^ extra addition
    }
    

    额外的, 2(std::cout &lt;&lt; "smth\n", throw 0, 2) 的类型更改为int,此时引用的规则不再适用——操作数不再是void 类型——因此不再有任何对操作数的限制。

    (现在我们可以质疑标准中的限制有什么意义......)

    【讨论】:

    • YMMV,但我认为throw((std::cout &lt;&lt; "smth\n"), 0) 使表达式最终抛出异常更加明显。 (虽然if (! b) { std::cout &lt;&lt; "smth\n"; throw 0; } int i = 2; 可能是最清楚的。)
    • @ruakh:确实,可能会更好。我将列出这两种选择。
    猜你喜欢
    • 2020-03-30
    • 2019-02-10
    • 1970-01-01
    • 1970-01-01
    • 2012-09-02
    • 1970-01-01
    • 1970-01-01
    • 2014-09-15
    • 2015-01-29
    相关资源
    最近更新 更多