【问题标题】:Is there a reference_wrapper<> for rvalue references?右值引用是否有reference_wrapper<>?
【发布时间】:2011-07-04 19:35:25
【问题描述】:

我想知道如何完成以下操作

void f(string &&s) { 
  std::string i(move(s)); 
  /* other stuff */ 
} 

int main() { 
  std::string s; 
  bind(f, s)(); // Error.
  bind(f, move(s))(); // Error.
  bind(f, ref(s))(); // Error.
}

如何传递右值引用并将其作为右值引用(可能已包装)存储在调用包装器中?我知道我可以手动编写一个像std::reference_wrapper&lt;&gt; 这样的类,它具有到T&amp;&amp; 的转换功能,但我宁愿避免这种情况并使用标准技术。


我按照 AProgrammer 的建议实现了它:

template<typename T> struct adv { 
  T t; 
  explicit adv(T &&t):t(forward<T>(t)) {} 
  template<typename ...U> T &&operator()(U &&...) { 
    return forward<T>(t); 
  } 
}; 

template<typename T> adv<T> make_adv(T &&t) { 
  return adv<T>{forward<T>(t)}; 
}

namespace std { 
  template<typename T> 
  struct is_bind_expression< adv<T> > : std::true_type {}; 
} 

现在我可以说

void f(string &&s) { 
  std::string i(move(s)); 
  /* other stuff */ 
} 

int main() { 
  std::string s; 
  bind(f, make_adv(move(s)))(); // Works!
}

如果我们将左值传递给make_adv,它会将其作为引用输入参数的左值转发,因此在这种情况下,它可以用作std::ref 的替代品。

【问题讨论】:

标签: c++ c++11 rvalue-reference stdbind


【解决方案1】:

我对此的看法。

N3225 中的 20.8.10.1.2/10

绑定参数 v1, v2, ..., vN 的值及其对应的类型 V1, V2, ..., VN 取决于从调用 bind 和调用包装器 g 的 cv 限定符 cv 派生的类型 TiD 为 如下:

  • 如果 TiD 为 reference_wrapper,则参数为 tid.get(),其类型 Vi 为 T&;
  • 如果 is_bind_expression::value 的值为真,则参数为 tid(std::forward(uj)...) 它的类型Vi是result_of::type;
  • 如果 is_placeholder::value 的值 j 不为零,则参数为 std::forward(uj) 它的类型 Vi 是 Uj&&;
  • 否则,值为 tid,其类型 Vi 为 TiD cv &。

因此,拥有右值引用的唯一可能性是让is_bind_expression&lt;TiD&gt;::value 为真或is_placeholder&lt;TiD&gt;::value 不为零。第二种可能性具有您不想要的含义,而用第一种实现想要的结果意味着如果我们限制为标准提供的类型,我们试图解决的问题就会得到解决。因此,唯一的可能性是提供您自己的包装器和 is_bind_expression&lt;TiD&gt; 的专门化(20.8.10.1.1/1 允许),因为我没有看到。

【讨论】:

  • 谢谢,我用is_bind_expression 实现了这个想法。我认为它足够通用,很有用,所以我会接受你的回答!
  • 非常感谢。这解决了我使用左值引用的 c++0x 线程函数的问题:) (g++4.6)
【解决方案2】:

如何传递右值引用并将其作为右值引用存储在调用包装器中?

这里的问题是这样的绑定函数对象可以被多次调用。如果函数对象将绑定参数作为右值转发,这显然只能工作一次。所以,这有点安全问题。

但在某些情况下,这种转发正是您想要的。您可以使用 lambda 作为中介:

bind([](string& s){f(move(s));},move(s));

基本上,我想出了这个 bind+lambda 组合作为缺少“移动捕获”的解决方法。

【讨论】:

  • 这是一个很好的理由,不能更直接地使用它。
【解决方案3】:

当我偶然发现这个问题时,我正在谷歌搜索“reference_wrapper for rvalues”。 不确定我的答案是否有用,它与 std::bind 无关,实际上也不能使用它,但对于其他一些用例,它可能会对某人有所帮助。

这是我实现 rvalue_reference_wrapper 的尝试:

#pragma once

#include <type_traits>
#include <memory>
#include <utility>

template<class T>
class rvalue_reference_wrapper
{
public:
    static_assert(::std::is_object<T>::value, "rvalue_reference_wrapper<T> requires T to be an object type.");

    using type = T;

    rvalue_reference_wrapper(T& ref_value) = delete;

    rvalue_reference_wrapper(T&& ref_value) noexcept
        : _pointer(::std::addressof(ref_value))
    {
    }

    operator T&&() && noexcept
    {
        return ::std::move(*_pointer);
    }

    T&& get() && noexcept
    {
        return ::std::move(*_pointer);
    }

    template<class... ArgTypes>
    auto operator()(ArgTypes&&... args) &&
        -> decltype(::std::invoke(::std::declval<rvalue_reference_wrapper<T>>().get(), ::std::forward<ArgTypes>(args)...))
    {
        return (::std::invoke(::std::move(*this).get(), ::std::forward<ArgTypes>(args)...));
    }

private:
    T* _pointer;
};

template<class T>
inline rvalue_reference_wrapper<T> rv_ref(T& ref_value) = delete;

template<class T>
inline ::std::enable_if_t<!(::std::is_lvalue_reference<T>::value), rvalue_reference_wrapper<T>> rv_ref(T&& ref_value) noexcept
{
    return rvalue_reference_wrapper<T>(::std::forward<T>(ref_value));
}

#ifdef _MSC_VER
namespace std
{
    template<class T>
    struct _Unrefwrap_helper<rvalue_reference_wrapper<T>>
    {
        using type = T &&;
        static constexpr bool _Is_refwrap = true;
    };
}
#else
#pragma error("TODO : implement...")
#endif

命名空间 std 中的最后一个特化允许 MSVC 的标准库实现与我的类型一起使用,例如使用 std::make_tuple 时:

int a = 42;
auto p_int = std::make_unique<int>(42);
auto test_tuple = std::make_tuple(42, std::ref(a), rv_ref(std::move(p_int)));
static_assert(std::is_same<decltype(test_tuple), std::tuple<int, int &, std::unique_ptr<int>&&>>::value, "unexpected result");

我相信为其他标准库实现实现类似的“解包”逻辑并不难。

【讨论】:

  • 这是合法的方式吗? std::addressof(const T&amp;&amp;) = delete;.
  • @MrBin 抱歉,我不确定我是否理解了这个问题。一种合法的方式来做什么?
  • : _pointer(::std::addressof(ref_value)) - ref_value 可以是左值或右值类型。对于右值std::addressof 被删除。
  • @MrBin 哦。是的,这是合法的,因为右值引用——如果它绑定到命名标识符(在本例中为“ref_value”)——本身就是一个左值。因此可以获取其地址。顺便说一句,这就是为什么我们必须在右值引用接收的参数上使用 std::move (再次!),以及为什么我们必须使用 std::forward 转发(“通用”)引用:“恢复”该对象的“右值”。
  • 我忘记了命名右值的这条规则。谢谢。
【解决方案4】:

您可以使用可变的 lambda 对象。

auto func = [=]() mutable {
    f(std::move(s));
};

【讨论】:

  • 对。但由于缺少“移动捕获”,字符串将被“复制捕获”。您甚至可以通过利用 bind 的移动捕获功能做得更好——请参阅我的答案。
猜你喜欢
  • 2021-06-14
  • 2011-04-12
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-22
相关资源
最近更新 更多