This question is a dual of "Constructor with by-value parameter & noexcept". That question showed that lifetime management of a by-value function argument is handled by the calling function; therefore the caller handles any exceptions that happen and the called-function can mark itself noexcept
. I'm wondering how the output end is handled with noexcept
.
MyType MyFunction( SomeType const &x ) noexcept;
//...
void MyCaller()
{
MyType test1 = MyFunction( RandomSomeType() );
MyType test2{ MyFunction( RandomSomeType() ) };
//...
test1 = MyFunction( RandomSomeType() );
test2 = std::move( MyFunction(RandomSomeType()) );
MyFunction( RandomSomeType() ); // return value goes to oblivion
}
Let's say that the return value is successfully created within MyFunction
. And let's say that the appropriate special member functions (copy/move-assignment/construction) of MyType
may not be noexcept
.
- Do the RVO/NRVO/Whatever-from-C++11 rules concerning the transfer of return values from the called function to the caller mean that the transfer always succeeds no-throw no matter the
noexcept
status of the appropriate special member function? - If the answer to the previous question is "no," then if the return value transfer throws, does the exception count against the called function or the caller?
- If the answer to the previous question is "the called function," then the plain
noexcept
marker onMyFunction
will cause a call tostd::terminate
. What shouldMyFunction
'snoexcept
profile be changed to? When I asked about this on the Usenet, a respondent thought it should bestd::is_nothrow_move_assignable<MyType>::value
. (Note thatMyCaller
used several methods of using a return value, butMyFunction
won't know which one is in use! The answer has to cover all cases.) Does it make a difference ifMyType
is changed to be copyable but non-movable?
So if the worst-cases of the second and third questions are accurate, then any function that returns by value can't have a plain noexcept
if the return type has a throw-able move! Now types with throw-able moves should be rare, but template code still has to "dirty" itself with is_nothrow_move_assignable
every time return-by-value is used.
I think making the called-function responsible is broken:
MyType MyFunction( SomeType const &x ) noexcept( ??? )
{
//...
try {
return SOME_EXPRESSION;
// What happens if the creation of SOME_EXPRESSION succeeds, but the
// move-assignment (or whatever) transferring the result fails? Is
// this try/catch triggered? Or is there no place lexically this
// function can block a throwing move!?
} catch (...) {
return MyType();
// Note that even if default-construction doesn't throw, the
// move-assignment may throw (again)! Now what?
}
}
This problem, to me at least, seems fixable at the caller's end (just wrap the move-assignment with a try
/catch
) but unfixable from the called-function's end. I think the caller has to handle this, even if we need to change the rules of C++ to do so. Or at least some sort of defect report is needed.