使用标准::绑定在可变参数模板中完美转发引用

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

本文关键字:完美 转发 参数 引用 变参 标准 绑定      更新时间:2023-10-16

我偶然发现了代码中的一个错误,我将其追溯到这样一个事实,即参数std::bind"...除非用std::refstd::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的引用传递的方法,同时尽可能保持完美的转发。我非常想通过在原点将其包裹在std::ref中来避免通过每个_args。相反,我希望在eval()内部自动找出引用。

我试过什么

bind_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,但我不确定如何捕获参数以实现完美的转发。

任何见解将不胜感激。

始终使用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))...)); 

是安全的。


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

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()更好。