Reputation: 93324
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 if nested2
'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 moving f
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)?
Upvotes: 3
Views: 319
Reputation: 50550
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.
Upvotes: 1