Home > OS >  Callback With Forwarding vs Without
Callback With Forwarding vs Without

Time:05-29

Lets say I have a function which triggers a callback.

template <typename Callback>
void call_callback(Callback&& rvalue_callback)

What is the difference between the following to calling snippets:

  1. no forwarding:

    {
         rvalue_callback();
    }
    
  2. with forwarding:

    {
         std::forward<Callback>(rvalue_callback)();
    }
    

A call example:

call_callback([]() { std::cout << "hello world" << std::endl; });

You can assume in your answer call_callback will always receive a lambda as a parameter and not an std::function for example.

More interested in practical differences then theoretical ones. For example performance or optimizations the compiler can do in one situation but not in another.

Thanks in advance,

CodePudding user response:

 ... rvalue_callback

First of all, note that you are dealing with a forwarding (or universal; common non-standard word) reference here, not an rvalue reference. For details, see e.g.:


What is the difference between the following to calling snippets:

You can assume in your answer call_callback will always receive a lambda as a parameter and not an std::function for example.

Given the assumption: none. std::forward is just a conditional cast (think std::move), but as you are not passing on the function argument rvalue_callback elsewhere (no "forwarding" in this context), and as the closure type of a lambda does not overload its function call operator on ref- or const-qualifiers of its implicit object parameter, the conditional cast in your case will essentially be a no-op (somewhat slightly different conversion sequence used for ranking viable overloads, but you only have one viable overload here).

If you were instead passing custom class-type object and invoked a member function, or wrote a functor which overloaded the ref-qualifiers of the implicit object parameter (which is not the case of the closure type of a lambda) then it could make a difference:

#include <iostream>

struct S {
    void f() &  { std::cout << "lvalue\n"; }
    void f() && { std::cout << "rvalue\n"; }
    // This kind of implicit object parameter 
    // overloading would not be present in the
    // closure type of a lambda.
    void operator()() &  { std::cout << "lvalue\n"; }
    void operator()() && { std::cout << "rvalue\n"; }
};

template<typename T>
void memfn1(T&& t) { t(); }

template<typename T>
void memfn2(T&& t) { std::forward<T>(t)(); }

template<typename T>
void funcallop1(T&& t) { t.f(); }

template<typename T>
void funcallop2(T&& t) { std::forward<T>(t).f(); }

int main() {
    S s{};
    memfn1(s);   // lvalue
    memfn2(s);   // lvalue
    memfn1(S{}); // lvalue !!!
    memfn2(S{}); // rvalue

    funcallop1(s);   // lvalue
    funcallop2(s);   // lvalue
    funcallop1(S{}); // lvalue !!!
    funcallop2(S{}); // rvalue
}

However this is forwarding, to an overloaded member function of the class-type function argument.

  • Related