15

With this code:

int main()
{
    try
    {
        throw -1;
    }
    catch (int& x)
    {
        std::cerr << "We caught an int exception with value: " << x << std::endl;
    }
    std::cout << "Continuing on our merry way." << std::endl;

    return 0;
}

We have:

/tmp$ ./prorgam.out
Continuing on our merry way
We caught an int exception with value: -1

How does the catch block read -1 as int&? We couldn't assign a value to a non-const lvalue reference.

And why is the second std::cout statement executed before the first std::cerr statement?

Ghasem Ramezani
  • 2,683
  • 1
  • 13
  • 32
  • 2
    Are you sure this is the exact output your get? The `We caught an int exception with value: -1` line should be printed first. – HolyBlackCat Dec 19 '19 at 14:54
  • What compiler are you using? [Can't reproduce](https://wandbox.org/permlink/2lsojyHpvs1e1DX7) the output. – Algirdas Preidžius Dec 19 '19 at 14:54
  • @AlgirdasPreidžius, GCC 9.2 in ArchLinux and MSVC2017 on Windows 10. – Ghasem Ramezani Dec 19 '19 at 14:59
  • @HolyBlackCat, This is exact output i get. – Ghasem Ramezani Dec 19 '19 at 15:00
  • @GhasemRamezani [Can't reproduce](https://wandbox.org/permlink/p5Ga8TH0GVzrUK81) on GCC 9.2 either. – Algirdas Preidžius Dec 19 '19 at 15:01
  • It's very surprising to me that this is allowed here *and* that it would be disallowed elsewhere. Seems to me like if this exception to the rule exists it requires that the compiler know how to handle a non-`const` reference to an rvalue to at least manage this case. So I don't understand why the standard disallows something that is then technically easy to allow (non-`const` reference to rvalue) and it goes out of it's way to allow it only in this case. It would make sense to me if it always disallowed it (for technical reasons) or always allowed it (because why not). – François Andrieux Dec 19 '19 at 15:02
  • 1
    @Scheff, Sorry you are right, The first output is redirected to `error stream` not `standard stream`. – Ghasem Ramezani Dec 19 '19 at 15:03
  • 3
    Duplicate of [Binding temporaries to non-const references in case of exceptions](https://stackoverflow.com/questions/26806857/binding-temporaries-to-non-const-references-in-case-of-exceptions). – François Andrieux Dec 19 '19 at 15:03
  • 2
    @FrançoisAndrieux The reason it is allowed is there are different semantics going on. Generally with a temporary you don't know what's going to happen to it so it was decided to only allow const references to temporaries. With exceptions, we know the lifetime of the object and we may want to modify it and rethrow it to a higher context. In order to facilitate that, the standard allows the binding to a non-const lvalue reference. – NathanOliver Dec 19 '19 at 15:05
  • Too funny. I couldn't reproduce (the re-ordering of outputs) on [coliru](http://coliru.stacked-crooked.com/a/c7e5876cdca05e24). Maybe, they fixed something meanwhile... :-) – Scheff's Cat Dec 19 '19 at 15:05
  • 1
    @FrançoisAndrieux `throw` creates a copy (or moves) the object you pass to it. The reference binds to that copy. It kind of makes sense that the copy is an lvalue. – HolyBlackCat Dec 19 '19 at 15:06
  • 1
    @HolyBlackCat Isn't that also what happens in `int foo(); const int & x = foo();`? – François Andrieux Dec 19 '19 at 15:07
  • @GhasemRamezani I can't reproduce even with GCC 9.2 _on Arch Linux_! That's pretty strange behavior. – Nonny Moose Dec 20 '19 at 01:53

2 Answers2

10

This is okay because of [except.throw]/3

Throwing an exception copy-initializes ([dcl.init], [class.copy.ctor]) a temporary object, called the exception object. An lvalue denoting the temporary is used to initialize the variable declared in the matching handler ([except.handle]).

emphasis mine

As you can see, even though it is a temporary, the compiler treats it as an lvalue for initializing the handler. Because of this, you don't need a const reference.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
8

From this throw reference:

Unlike other temporary objects, the exception object is considered to be an lvalue argument when initializing the catch clause parameters, so it can be caught by lvalue reference, modified, and rethrown.

So while the "object" is temporary, it's still an lvalue and as such you can catch it by reference.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621