`throw` 可以在 C++ 条件(三元)运算符中的逗号子表达式内吗?

Fed*_*dor 23 c++ conditional-operator throw language-lawyer

众所周知,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
};
Run Code Online (Sandbox Code Playgroud)

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

虽然错误信息:

#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
};
Run Code Online (Sandbox Code Playgroud)

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

根据https://en.cppreference.com/w/cpp/language/operator_other,似乎 GCC/Clang 是正确的,因为

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

在这里我们用括号括起来的逗号表达式只是用 throw-expression 结束。

根据标准,MSVC 在接受示例的最后一行时是否不正确?

Sto*_*ica 19

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

[表达式条件]

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

  • 第二个或第三个操作数(但不是两个)是一个(可能带括号的)抛出表达式[expr.throw]);结果是另一个的类型和值类别。的条件表达式是一个位字段如果操作数是位字段。
  • 第二个和第三个操作数都有类型void;结果是类型void并且是纯右值。

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

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

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


Mat*_* M. 8

正如@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
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • YMMV,但我认为 `throw((std::cout &lt;&lt; "smth\n"), 0)` 使得表达式最终抛出异常更加明显。(虽然 `if (! b) { std::cout &lt;&lt; "smth\n"; throw 0; } int i = 2;` 可能是最清楚的。) (2认同)