It seems that both order of function argument evaluation, as well as the order of lambda capture initializers, is unspecified by the C++ standard.
(See http://en.cppreference.com/w/cpp/language/lambda as well as Order of evaluation in C++ function parameters)
This causes some concern for me due to how it may interact with move semantics.
Suppose I have an object of type T
that may have a copy or move constructor that throws. Then suppose I have a move-only object, such as an std::promise
. Consider the following situation:
T value; // some type that potentially throws when moved or copied
promise<U> pr; // a promise whose result is some type U
future<U> fut = pr.get_future();
std::thread(
[v = std::move(value), pr = std::move(pr)]() {
try {
// do some stuff
pr.set_value(/* whatever */);
}
catch (...) { pr.set_exception(std::current_exception()); }
}
).detach();
// return the future
Now, we have a try/catch block that is executed inside the std::thread
, but we don't have any exception handling for anything that might go wrong while initializing the thread. Specifically, what can we do if the expression v = std::move(value)
in the lambda capture list ends up throwing an exception? Ideally, we'd want to handle it with a try-catch block and then just call pr.set_exception(...)
, like this:
try {
std::thread(
[v = std::move(value), pr = std::move(pr)]() {
try {
// do some stuff
pr.set_value(/* whatever */);
}
catch (...) { pr.set_exception(std::current_exception()); }
}
).detach();
}
catch (...) {
pr.set_exception(std::current_exception());
}
There's just one major problem: when we get to our outer catch block, we don't know if the expression pr = std::move(pr)
has already been called, because we have no guarantee about the order for a list of lambda-capture initializers. So when we say pr.set_exception(...)
we don't know if our promise is even valid anymore, because we don't know if the promise was move-constructed before the expression v = std::move(value)
was evaluated.
So how can we handle the case where the move or copy constructor for T
might throw?
The only solution I can think of - maybe - is to wrap the lambda in a call to std::bind
, like this:
std::thread(
std::bind(
[v = std::move(value)](promise<U>& pr) {
// ...
},
std::move(pr)
)
).detach();
Here, even though we don't have any guarantee about the order of function argument evaluation either, it's my understanding that we're still guaranteed that the expression v = std::move(value)
would need to be evaluated before the promise is actually move constructed, since the expression std::move(pr)
doesn't actually move construct the promise - it just casts it to an R-value. The promise would only be move-constructed later, inside the call to std::bind
, but not as an effect of one of the function arguments being evaluated.
However, I'm not entirely sure about this solution. I'm not sure if the standard somehow may still allow for the compiler to move-construct the promise before T
is move/copy constructed.
So, does my solution using std::bind
solve this problem? If not, what are some ways to solve this?
std::unique_ptr
to make it safely movable if that's an acceptable solution. – Abscissa