在variadic模板中使用std :: bind完美转发引用

时间:2017-10-04 12:26:40

标签: c++ c++14 variadic-templates perfect-forwarding stdbind

我偶然发现了我的代码中的一个错误,我追溯到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的原点来传递每个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; } ,但我不知道如何捕获参数以实现完美的转发。

任何见解都将受到赞赏。

1 个答案:

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