15

A code snippet I saw in Effective Modern C++ has a clever implementation of the instrumentation rationale to create a function timer :

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params)
    {
        start timer; 
        std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...); 
        stop timer and record elapsed time; 
    };

My question is about std::forward<decltype(func)>(func)(...

  • To my understanding, we are actually casting the function to its original type, but why is this needed? It looks like a simple call would do the trick.
  • Are there any other cases where we use perfect forwarding to make a function call ?

This looks like a good use case for the use of familiar template syntax in lambda expressions in case we wanted to make the timer type a compile time constant.

Community
  • 1
  • 1
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • I think it could be because `func` can big function object with a non-const operator(). This way, you avoid pointless copies and you allow mutation of the function object – KABoissonneault Jul 06 '15 at 18:50
  • 1
    The *type* of the expression doesn't change with the cast, but its *value category* does. That's the whole and only [point of `forward`](http://stackoverflow.com/a/13219621). – Kerrek SB Jul 06 '15 at 18:55
  • 6
    The intent is to make sure the correct [ref-qualified](http://en.cppreference.com/w/cpp/language/member_functions#const-.2C_volatile-.2C_and_ref-qualified_member_functions) overload of `operator()` is chosen. – Tavian Barnes Jul 06 '15 at 18:56
  • 1
    The type of func, once in the function, is not an r-value reference anymore (assuming it was when passed in). If it were, every time you'd pass it to a function it would try to move it! That's why `std::forward` is needed. – Cameron Jul 06 '15 at 18:56
  • @Cameron nothing gets passed to another function here. My question is for this specific context. The reason pointed by Tavian Barnes (and implied by Kerrek) seems convincing – Nikos Athanasiou Jul 06 '15 at 19:02
  • @Nikos: Ah, sorry, I read too fast. My comment applies to the usual argument passing case, but that's obviously not what you're doing with `func`. Please ignore :-) – Cameron Jul 06 '15 at 20:36

1 Answers1

18

A better description of what std::forward<decltype(func)>(func)(...) is doing would be preserving the value category of the argument passed to the lambda.

Consider the following functor with ref-qualified operator() overloads.

struct foo
{
    void operator()() const &&
    { std::cout << __PRETTY_FUNCTION__ << '\n'; }

    void operator()() const &
    { std::cout << __PRETTY_FUNCTION__ << '\n'; }
};

Remember that within the body of the lambda func is an lvalue (because it has a name). If you didn't forward the function argument the && qualified overload can never be invoked. Moreover, if the & qualified overload were absent, then even if the caller passed you an rvalue foo instance, your code would fail to compile.

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Would you agree that the `decltype(func)` part is only needed in generic lambdas where no type information is spelled out for `func` ? If that's the case I could write `std::forward(func)(...)` for `template void Foo(F&& func)` and do the same thing right ? – Nikos Athanasiou Oct 18 '15 at 00:36
  • @NikosAthanasiou Yes, that would do the same thing – Praetorian Oct 18 '15 at 20:11
  • I didn't understand why this matters until I realized that `func` could be not only a function pointer, a lambda or `std::function` instance, it could be an instance of any class that declares `operator() &&`. Only then it becomes important. – Violet Giraffe Feb 05 '19 at 15:57