我偶然发现了我的代码中的一个错误,我追溯到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::ref
或std::cref
中。
将参数作为对_f
的引用传递的方法,同时尽可能保留完美转发。我非常希望避免通过将_args
包裹在std::ref
的原点来传递每个eval()
。相反,我希望在std::ref
内自动找出引用。
std::forward
bind
_args
似乎有效时,使用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
:
std::bind
但我不知道这与rvalue引用有什么关系。始终使用function wpcd_add_dealer_meta_boxes() {
add_meta_box(
'dealer_first_name',
'First Name',
array($this, 'wpcd_meta_box_first_name_markup'),
'dealers',
'normal',
'default',
array(
'id' => 'first_name',
'name' => 'first_name',
'type' => 'text',
'placeholder' => 'Enter first name',
'maxlength' => '30',
'spellcheck' => 'true',
'autocomplete' => 'off'
)
);
}
是否安全?这是一种矫枉过正吗?我还有完美的转发吗?问题比比皆是。
我还考虑用普通的lambda替换function wpcd_meta_box_first_name_markup($args) {
$html = '<input ';
$html.= 'type="' . $args->type . '" ';
$html.= 'id="' .$args->id . '" ';
$html.= 'name="' .$args->name . '" ';
if( isset($args->required) && ($args->required == 'true' || $args->required == '1' ) ) {
$html.= 'required ';
}
if( isset($args->placeholder) && $args->placeholder != '' ) {
$html.= 'placeholder="' . esc_attr( $args->placeholder ) . '" ';
}
if( isset($args->maxlength) ) {
$html.= 'maxlength="' . $args->maxlength . '" ';
}
if( isset($args->spellcheck) && ($args->spellcheck == 'true' ) ) {
$html.= 'spellcheck="' . $args->spellcheck . '" ';
}
if( isset($args->autocomplete) && ($args->autocomplete == 'on' ) ) {
$html.= 'autocomplete="' . $args->autocomplete . '" ';
}
$html.= '/>';
echo $html;
}
,但我不知道如何捕获参数以实现完美的转发。
任何见解都将受到赞赏。
答案 0 :(得分:2)
始终使用
std::ref
安全吗?
与正常使用参考文件一样安全。因此,在这种情况下,如果func
不会比eval()
功能更长,那么它是安全的。即使我传入rvalues,参考文献也不会晃来晃去。但是如果你需要在某个地方存储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); }
lvalues变成引用包装器,rvalues保持为rvalue引用。现在:
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()
会更好。