Capturing generic callable objects in nested lambdas - always forward?

有些话、适合烂在心里 提交于 2019-12-22 18:01:25

问题


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:

  1. 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.

  2. 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.

  3. 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)?


回答1:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!