23

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?

user3840170
  • 26,597
  • 4
  • 30
  • 62
Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 2
    fyi and does not explain the difference try `i = b ? 2 : (std::cout << "smth\n", throw 0, 42);` this makes the types of the sub-expressions (E2 and E3) the same. – Richard Critten Aug 14 '21 at 08:52
  • @RichardCritten, thanks for the idea. This is indeed accepted by all: https://gcc.godbolt.org/z/85YGz9ffG – Fedor Aug 14 '21 at 09:36

2 Answers2

19

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.

muru
  • 4,723
  • 1
  • 34
  • 78
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
8

As noted by @StoryTeller - Unslander Monica, there is a specification restriction with operands evaluating to void.

I should note, however, that in this case the restriction is trivially bypassed:

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

The extra , 2 changes the type of (std::cout << "smth\n", throw 0, 2) to int, at which point the quoted rule no longer applies -- the operand is no longer of type void -- and therefore there is no longer any restriction on the operand.

(And now we can question what is the point of the restriction in the standard...)

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 2
    YMMV, but I think that `throw((std::cout << "smth\n"), 0)` makes it more visibly obvious that the expression ends up throwing an exception. (Though `if (! b) { std::cout << "smth\n"; throw 0; } int i = 2;` is probably clearest.) – ruakh Aug 14 '21 at 23:31
  • @ruakh: Indeed, may be better. I'll list both alternatives. – Matthieu M. Aug 15 '21 at 10:05