【问题标题】:Perfect forwarding of references with std::bind inside variadic template使用可变参数模板内的 std::bind 完美转发引用
【发布时间】:2017-10-04 12:26:40
【问题描述】:

我偶然发现了我的代码中的一个错误,我将其追溯到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 的引用传递的方法,同时尽可能保持完美转发。我非常希望通过在原点将std::ref 包装在_args 中来避免传递每个_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,但我不确定如何捕获参数以实现完美转发。

任何见解都将不胜感激。

【问题讨论】:

  • 如果 arg 是一个右值,那么你会遇到问题,如果你引用一个引用,并在 eval 之外传递 func。因此,您想改用std::ref(std::forward&lt;Args&gt;(_args))。如果 arg 是右值,这将使编译器失败。除此之外,我认为这个解决方案很好(如果你不将func 传递给函数之外,那么你可以使用当前的解决方案)
  • 谢谢,这很有用。如果我必须在eval() 之外通过func,你知道我能做什么吗?
  • 那你需要申请copy:在这种情况下不要使用std::ref。我认为这可以通过一些模板专业化来完成。
  • 或仅使用函数重载:创建一个类似std::ref 的函数,它也适用于右值引用(在左值引用的情况下,它应该返回std::reference_wrapper,在右值引用的情况下,它应该返回右值引用)。

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


【解决方案1】:

始终使用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() 更好。

【讨论】:

  • 很棒的答案,谢谢! lambda 选项非常有趣,但可以将它作为std::function 传递到eval() 之外吗?也感谢您指出确定返回类型的正确方法。
  • @cantordust lambda 拥有一切(就像您使用 bind 的初始实现拥有一切一样),所以两者都是安全的。当您开始持有必须开始仔细思考的参考资料时。
  • 我将bind() 解决方案集成到我的程序中,效果非常好,谢谢。我也会尝试 lambda 版本,因为它看起来更简单。
猜你喜欢
  • 2013-08-25
  • 1970-01-01
  • 2013-01-06
  • 2011-09-23
  • 2015-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-26
相关资源
最近更新 更多