在可变参数模板中使用 std::bind 完美转发引用

Perfect forwarding of references with std::bind inside variadic template

我在我的代码中偶然发现了一个错误,我追溯到 std::bind "...are never passed by reference unless wrapped in std::ref or std::cref".

的参数这一事实

我有什么

我有一个看起来像这样的函数模板(去除了不相关的位):

template<typename F, typename ... Args>
auto eval(F&& _f, Args&&... _args)
{
    using ret_t = typename std::result_of<F&&(Args&&...)>::type;

    std::function<ret_t()> func(std::bind(std::forward<F>(_f), std::forward<Args>(_args)...)));
    // Etc.
}

到目前为止一切顺利,但是如果函数 _f 将引用作为参数,这就会崩溃,因为参数被复制或移动,除非包含在 std::refstd::cref 中。

我想要什么

一种将参数作为对 _f 的引用传递的方法,同时尽可能保留完美转发。我非常想通过将每个 _args 包装在原点的 std::ref 中来避免必须通过每个 _args 。相反,我希望在 eval().

中自动计算出引用

我试过的

binding _args 似乎有效时,使用 std::ref 而不是 std::forward

template<typename F, typename ... Args>
auto eval(F&& _f, Args&&... _args)
{
    using ret_t = typename std::result_of<F&&(Args&&...)>::type;

    // Replace std::forward<Args>() with std::ref()
    std::function<ret_t()> func(std::bind(std::forward<F>(_f), std::ref(_args)...))); 
    // Etc.
}

但我不知道这对右值引用有何影响。总是使用 std::ref 安全吗?这是矫枉过正吗?我还有完美转发吗?问题比比皆是。

我也考虑过用通用的 lambda 替换 std::bind,但我不确定如何捕获参数以实现完美转发。

如有任何见解,我们将不胜感激。

Is it safe to always use std::ref?

它和正常使用引用一样安全。所以在这种情况下,如果 func 没有超过函数 eval(),那么它是安全的。即使我传入右值,引用也不会悬空。但是,如果您需要在某处存储 func,那么这就是悬挂引用的秘诀。

您想要做的是有条件地将它们包装为参考。一种方法是为左值和右值提供两个重载:

template <class T> std::reference_wrapper<T> maybe_wrap(T& val) { return std::ref(val); }
template <class T> T&& maybe_wrap(T&& val) { return std::forward<T>(val); }

左值变为引用包装器,右值保留为右值引用。现在:

std::function<ret_t()> func(std::bind(std::forward<F>(_f), 
    maybe_wrap(std::forward<Args>(_args))...)); 

安全。


请注意,调用绑定表达式的方式与您确定 return 类型的方式并不完全匹配。所有绑定参数都作为左值传递,所以你真正需要的是:

using ret_t = typename std::result_of<F&(Args&...)>::type;
//                                    ^^^^^^^^^^^

您也可以将其替换为 lambda,这很尴尬,因为捕获参数包的方式有限。你必须通过一个元组并实现 std::apply() (这在 C++14 中是可行的):

[f=std::forward<F>(f),
    args=std::make_tuple(std::forward<Args>(_args)...)]() -> decltype(auto)
{
    return std::apply(f, args);
}

不过,由于存在必要的障碍,而且无论如何都会被类型擦除,也许 bind() 更好。