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?
Capture clauseA lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.
The order of precedence can be altered by using parentheses around the parts of the mathematical expression that needs to be evaluated first.
Postfix expressions are evaluated from left to right. This includes functions calls and member selection expressions. Assignment expressions are evaluated from right to left. This includes compound assignments.
C++ Lambda expression allows us to define anonymous function objects (functors) which can either be used inline or passed as an argument. Lambda expression was introduced in C++11 for creating anonymous functors in a more convenient and concise way.
Your std::bind
works (bind
takes arguments by reference, the closure object's initialization is sequenced before the execution of bind
's body, and the move from the promise
necessarily happens inside bind
).
It's, however, rather pointless since std::thread
's constructor can already pass along arbitrary arguments.
std::thread(
[v = std::move(value)](promise<U> pr) {
// ...
},
std::move(pr)
).detach();
Note that std::thread
passes the arguments as rvalues, unlike bind
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With