c++template-meta-programmingperfect-forwarding

Generic Perfect Forwarding of Function’s Result


I encountered an issue when trying to perfect forward a function’s result in a generic way.

Here two functions that provide a result:

Foo provideFooAsTemporary() 
{
    return Foo{};
}

Foo& provideFooAsReference() 
{
    static Foo foo{};
    return foo;
}

Here the erroneous perfect forwarding:

template <typename TFn>
decltype(auto) erroneousForwardFoo(TFn&& fn) 
{
        auto&& result = fn();

        // do some stuff here

        return std::forward<decltype(result)>(result);
}

Foo fooTemporary = erroneousForwardFoo(provideFooAsTemporary); // fails
Foo& fooReference = erroneousForwardFoo(provideFooAsReference); // works

When the result is a temporary, the forwarding fails, which is indicated by the result from erroneousForwardFoo pointing to invalid memory.

In contrast, the following simplified forwarding code works like a charm:

template <typename TFn>
decltype(auto) workingForwardFoo(TFn&& fn) 
{
        return fn();
}

Foo fooTemporary = workingForwardFoo(provideFooAsTemporary); // works
Foo& fooReference = workingForwardFoo(provideFooAsReference); // works

However, I want to "do some stuff" before returning, so I need a fix for erroneousForwardFoo.

Debugging has shown that a dangling reference is the problem (see godbolt example). As I understand, this is because the lifetime of the temporary is bound to the rvalue reference result, and unfortunately moving doesn’t change this.

Now the questions:

  1. What is the detailed and accurate explanation of what’s going wrong?
  2. How can I rewrite erroneousForwardFoo in a lean way to achieve a correct generic implementation?

EDIT 1: Although I only provided a code example for forwarding a lvalue reference, I want the solution to support rvalue references too:

Foo&& provideFooAsRValueReference() 
{
    static Foo foo{};
    return std::move(foo);
}

Solution

  • decltype(auto) can be used for objects too. I would simply use static_cast:

    decltype(auto) forwarder(auto&& ...fn_or_arg){
        decltype(auto) res = std::invoke(std::forward<decltype(fn_or_arg)>(fn_or_arg)...);
    
        /*Manipulate(res)...*/;
    
        return static_cast<decltype(res)>(res);
    };
    

    If compiler has difficulty with direct initialization and copy elision, it is possible to return conditionally:

    using res_t = decltype(res);
    if  constexpr (std::is_reference_v<res_t>)
        return std::forward<res_t>(res);
    else
        return res;