Is there a difference between "throw MyException()" and "throw (MyException())"?

The exception object is copy-initialised (except.throw/3), so it doesn't really matter which one you use; the result is the same.

Copy-initialising would ignore a reference qualifier even if you got one out of this.

We can prove this with some trace output:

#include <cstdio>
using std::printf;

struct T
{
    T() { printf("T()\n"); }
    ~T() { printf("~T()\n"); }
    T(const T&) { printf("T(const T&)\n"); }
    T(T&&) { printf("T(T&&)\n"); }
    T& operator=(const T&) { printf("T& operator=(const T&)\n"); return *this; }
    T& operator=(const T&&) { printf("T& operator=(T&&)\n"); return *this; }
};

int main()
{
    try
    {
        throw T();
    }
    catch (const T&) {}
}

Even if you switch from throw T() to throw (T()) the semantics (and output) are exactly the same:

T()
T(T&&)
~T()
~T()

That is, a temporary T() is constructed, then moved into the real exception object (which exists in some magical "safe space"), and ultimately both are destructed.

Note that, to see this proof, you do have to go back to C++14 (as C++17 doesn't materialise that temporary until the real exception object is needed, per so-called "mandatory elision") and turn off pre-C++17 optional elision (e.g. -fno-elide-constructors in GCC).

If, as some others have claimed, there could be such a thing as "throwing a reference" (which became dangling), you'd only ever see one construction of T. In truth, there is no such thing as expressions of reference type, despite the best efforts of decltype and auto to pretend to you that there are.

In both cases, the expression we give to throw is an rvalue MyException.

The reason we have to be careful with return when using a deduced return type (via decltype(auto)), is that deduction considers value category. If you have a local variable int x, then in return x the expression x is xvalue int, so your deduced type will be int and everything is fine. If you write return (x) instead then the expression is lvalue int, which results in a deduced type of int& and suddenly you have problems. Notice that neither expression has reference type (a thing which effectively does not exist). But exactly none of this is relevant in your throw scenario anyway, not least of all because your argument is a temporary in the first place.

Tags:

C++

Exception