Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::bind and perfect forwarding

The following code does not compile:

#include <functional>

template<class ...Args>
void invoke(Args&&... args)
{
}

template<class ...Args>
void bind_and_forward(Args&&... args)
{
    auto binder = std::bind(&invoke<Args...>, std::forward<Args>(args)...);
    binder();
}

int main()
{
    int a = 1;
    bind_and_forward(a, 2);
}

If I understand correctly, the reason is as follows: std::bind copies its arguments, and when the binder's operator() is called, it passes all the bound arguments as lvalues - even those ones that entered bind as rvalues. But invoke was instantiated for the original arguments, and it can't accept what the binder attempts to pass it.

Is there any solution for this problem?

like image 287
Igor R. Avatar asked Jun 21 '15 19:06

Igor R.


People also ask

What is C++ 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.

Is std :: bind deprecated?

Yes: std::bind should be replaced by lambda For almost all cases, std::bind should be replaced by a lambda expression. It's idiomatic, and results in better code. There is almost no reason post C++11 to use std::bind .

What is the use of std :: bind?

std::bind is a Standard Function Objects that acts as a Functional Adaptor i.e. it takes a function as input and returns a new function Object as an output with with one or more of the arguments of passed function bound or rearranged.

What is the use of std :: forward?

std::forward If arg is an lvalue reference, the function returns arg without modifying its type. This is a helper function to allow perfect forwarding of arguments taken as rvalue references to deduced types, preserving any potential move semantics involved.


1 Answers

Your understanding is correct - bind copies its arguments. So you have to provide the correct overload of invoke() that would be called on the lvalues:

template<class ...Args>
void bind_and_forward(Args&&... args)
{
    auto binder = std::bind(&invoke<Args&...>, std::forward<Args>(args)...);
                                    ^^^^^^^^
    binder();
}

This works on most types. There are a few exceptions enumerated in [func.bind.bind] for operator(), where Arg& is insufficient. One such, as you point out, is std::reference_wrapper<T>. We can get around that by replacing the Args&usage above with a type trait. Typically, we'd just add an lvalue reference, but for reference_wrapper<T>, we just want T&:

template <typename Arg>
struct invoke_type 
: std::add_lvalue_reference<Arg> { };

template <typename T>
struct invoke_type<std::reference_wrapper<T>> {
    using type = T&;
};

template <typename T>
using invoke_type_t = typename invoke_type<T>::type;

Plug that back into the original solution, and we get something that works for reference_wrapper too:

template<class ...Args>
void bind_and_forward(Args&&... args)
{
    auto binder = std::bind(&invoke<invoke_type_t<Args>...>, 
                            //      ^^^^^^^^^^^^^^^^^^^
                            std::forward<Args>(args)...);
    binder();
}

Of course, if one of Arg is a placeholder this won't work anyway. And if it's a bind expression, you'll have to write something else too.

like image 135
Barry Avatar answered Oct 06 '22 21:10

Barry