Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does std::function support perfect forwarding?

Tags:

c++

templates

Does std::function support perfect forwarding of arguments? I decided to test it:

struct Widget
{
    void operator()(int const&)
    {
        std::cout << "lvalue";
    }

    void operator()(int&&)
    {
        std::cout << "rvalue";
    }
};

std::function<void(int)> f{Widget()};
int x = 5;
f(x);

Guess which operator() gets called - the one taking rvalue reference. It seems that it is by design. What is the rationale behind this behavior?

like image 753
rubix_addict Avatar asked Apr 01 '18 20:04

rubix_addict


People also ask

What is C++ perfect forwarding?

What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.

What does std :: forward do?

The std::forward function as the std::move function aims at implementing move semantics in C++. The function takes a forwarding reference. According to the T template parameter, std::forward identifies whether an lvalue or an rvalue reference has been passed to it and returns a corresponding kind of reference.

What is the difference between std :: forward and std :: move?

std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.

What is forwarding reference in C++?

When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.


1 Answers

Yes and no. Yes, the arguments are forwarded. But no, overload resolution is not done based on the arguments you provide at the point of the call - it's done based on the template arguments to std::function.

std::function<void(int)> means you have a callable that takes an int, which implicitly is an int rvalue. Calling Widget with an rvalue of type int prefers the int&& overload over the int const& overload, which is why that one is selected. It doesn't matter how you invoke the actual function object, once you selected that you want void(int) - that's the preferred overload.

The actual call operator behaves as if:

struct widget_function {
    void operator()(int arg) const {
//  ~~~~~           ~~~~
        w_(std::forward<int>(arg));
//                      ~~~
    }

    Widget w_;
};

The underlined portions come from the signature you provide to std::function. You can see that both f(x) and f(5) end up calling the same operator() from Widget.


In short, std::function<Sig> type erases one single overload that satisfies Sig. It does not type erase an entire overload set. You asked for void(int), so you get void(int) and only void(int).

like image 57
Barry Avatar answered Nov 14 '22 22:11

Barry