I have various functions in my codebase that take a generic callable object and pass it to a series of nested lambdas before calling it. Example:
template <typename TF>
void interface(TF&& f)
{
nested0([/*...*/]()
{
nested1([/*...*/](auto& x)
{
nested2([&x, /*...*/]()
{
f(x);
});
});
});
}
Note that interface
is taking a callable object of type TF
by forwarding reference (previously known as universal reference). The callable object is usually a lambda with various captured variables, both by value and by reference.
What is the best (in terms of performance) way of capturing f
in the nested lambdas while maintaining correctness?
I can think of three options:
Capture
f
by copy.nested0([f]() { nested1([f](auto& x) { nested2([&x, f]() { f(x); }); }); });
Could cause unnecessary copies, and if the captured object is
mutable
it could cause incorrect behavior.Capture
f
by reference.nested0([&f]() { nested1([&f](auto& x) { nested2([&x, &f]() { f(x); }); }); });
Seems reasonable, but could cause problems if any of the nested lambdas perform an action that outlives the owner of
f
. Imagine ifnested2
's body was executed in a separate thread -f
could already be out of scope.Make the lambdas
mutable
and capture by perfect-forwarding.#define FWD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__) nested0([f = FWD(f)]() mutable { nested1([f = FWD(f)](auto& x) mutable { nested2([&x, f = FWD(f)]() mutable { f(x); }); }); });
The lambdas have to be
mutable
because we're potentially movingf
from a lambda to another one. This approach seems to avoid unnecessary copies and to correctly move the callable object if it needs to outlive the original caller.
Is option 3 always the best one, or does it have any potential drawback? ...or maybe there is no "best and correct" way at all (knowledge about the callable object is required)?
As mentioned in the comments, it's difficult to say with such a poor context given about the problem.
That said, the best solution seems to me to capture everything by reference and break this logic whenever you need a copy the aim of which is to outlive the lifetime of f
, as an example:
nested0([&f]() {
n1([&f](auto& x) {
n2([&x,
// get a local copy of f
f{std::forward<TF>(f)}]() {
f(x);
});
});
});
Anyway, there is no rule of thumb for such a problem.
The best solution is tightly bound likely to the actual problem.
As usual. Fair enough.
来源:https://stackoverflow.com/questions/36792059/capturing-generic-callable-objects-in-nested-lambdas-always-forward