c++c++17perfect-forwardingstd-invoke

C++: does std::invoke need perfect forwarding?


Snippet

template <typename CallableType, typename... Args>
auto invokeTest(CallableType&& fn, Args&&... args)
{
    return std::invoke(fn, std::forward<Args>(args)...);
}

Is std::forward<Args> needed here? Or is it enough to write

template <typename CallableType, typename... Args>
auto invokeTest(CallableType&& fn, Args&&... args)
{
    return std::invoke(fn, args...);
}

What are the differences?


Solution

  • Well, I'd say yes but it depends on the case.

    You don't need forwarding in that case:

    void bar(int);
    
    int main() {
        int i = 0;
        invokeTest(bar, i);
    }
    

    But, you'll need forwarding for this case:

    void foo(std::unique_ptr<int>);
    
    int main() {
        auto p = std::make_unique<int>();
        invokeTest(foo, p); // Won't work, cannot copy unique_ptr
    }
    

    When writing std::invoke(fn, args...), the args... are all lvalues, at which point I would recommend taking parameters by Args const&... to be clearer.

    If you forward, then you move the object that were rvalues in your parameters:

    template <typename CallableType, typename... Args>
    auto invokeTest(CallableType&& fn, Args&&... args)
    {
        return std::invoke(fn, std::forward<Args>(args)...); // all forward value caterories
    }
    
    invokeTest(foo, std::move(p)); // works.
    

    If you use Args&&..., use forwarding.