I believe this can be answered without language-lawyering.
X f() {return ...;}Y y = f();
This in general involves two copies (or two objects being created, rather), unless elided.
First ...
is copied into the returned temporary (of type X
), and then that temporary is copied into Y y
.
The second one is always elided if X
and Y
are the same type (since C++17) (including if Y
is auto
). The first one is elided if you return a prvalue.
If the first one throws, the function can catch it. If the second one throws, only the caller can catch it.
This makes sense because the first copy happens relatively early, before the local variables are destroyed even. So it would make little sense to prevent the function from catching exceptions during this copy, but then allowing catching exceptions from local variables' destructors, which can be thrown after that.