std::bind 并完美转发

std::bind and perfect forwarding

以下代码无法编译:

#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);
}

如果我理解正确的话,原因如下:std::bind复制它的参数,当调用binderoperator()时,它传递所有绑定参数作为lvalues - 即使是那些输入 bind 作为 rvalues 的人。但是 invoke 是为原始参数实例化的,它不能接受 binder 试图传递给它的内容。

这个问题有解决办法吗?

你的理解是正确的——bind复制它的论点。因此,您必须提供将在左值上调用的 invoke() 的正确重载:

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

这适用于大多数类型。在 [func.bind.bind] 中为 operator() 列举了一些例外情况,其中 Arg& 是不够的。正如您所指出的,其中之一是 std::reference_wrapper<T>。我们可以通过用类型特征替换上面的 Args& 用法来解决这个问题。通常,我们只是添加一个左值引用,但对于 reference_wrapper<T>,我们只需要 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;

将其重新插入原始解决方案,我们得到的东西也适用于 reference_wrapper

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

当然,如果 Arg 之一是占位符,这无论如何都行不通。如果它是一个绑定表达式,您还必须编写其他内容。