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?
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.
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 .
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With