Search code examples
c++language-lawyerconditional-operatorthrow

Can `throw` be inside a comma subexpression within C++ conditional (ternary) operator?


It is well known that throw can be placed as the second or the third operand of C++ ternary operator ?:. But can it be inside a comma subexpression of there operands? It looks like compilers diverge in this regard. Please consider an example:

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

This example is accepted by MSVC, but rejected by both GCC and Clang, demo: https://gcc.godbolt.org/z/6q46j5exP

Though the error message:

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

suggests that it was not rejected intentionally but rather the compiler thinks that the third operand not only has formal type void but actually can return.

According to https://en.cppreference.com/w/cpp/language/operator_other, it seems that GCC/Clang are right since

Either E2 or E3 (but not both) is a (possibly parenthesized) throw-expression.

and here we have parenthesized comma expression just finishing with throw-expression.

According to the standard, is MSVC incorrect in accepting the last line of the example?


Solution

  • Clang and GCC are correct to reject it. It's pretty straightforward:

    [expr.cond]

    2 If either the second or the third operand has type void, one of the following shall hold:

    • The second or the third operand (but not both) is a (possibly parenthesized) throw-expression ([expr.throw]); the result is of the type and value category of the other. The conditional-expression is a bit-field if that operand is a bit-field.
    • Both the second and the third operands have type void; the result is of type void and is a prvalue.

    The wording is pretty precise here. It says one operand is a throw-expression when the first bullet applies. And (std::cout << "smth\n", throw 0) is not a throw-expression. It's parenthesized comma expression.

    So we can only be in the case of the second bullet, but its conditions don't hold either. So a "shall" requirement is broken, and the program is thus ill-formed.

    Now, MSVC may be offering an extension around this, but it's not standard.