是否存在适用于右值引用的reference_wrapper<>?

17
我想知道如何完成以下操作。
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<>,该类具有转换函数到T&&,但我更愿意避免这样做并使用标准技术。
我按照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的替代品在这种情况下使用。


1
参见:https://dev59.com/nG445IYBdhLWcg3wappH - GManNickG
4个回答

7

以下是我的看法:

N3225中的20.8.10.1.2/10规定了绑定参数v1、v2、...、vN及其对应类型V1、V2、...、VN的值取决于从调用bind派生的类型TiD以及调用包装器g的cv限定符cv,具体如下:

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

因此,拥有rvalue引用的唯一可能性是is_bind_expression<TiD>::value为true或is_placeholder<TiD>::value不为零。第二种情况会带来不必要的影响,而通过第一种方式达到所需结果将意味着我们只能使用标准提供的类型来解决问题。因此,唯一的可能性是提供您自己的包装器,并为is_bind_expression<TiD>提供特化(这是20.8.10.1.1/1允许的),但我没有找到一个。


谢谢,我使用 is_bind_expression 实现了这个想法。我认为它足够通用以便有用,所以我会接受你的答案! - Johannes Schaub - litb
非常感谢。这解决了我的问题,关于一个接受左值引用的c++0x线程函数 :) (g++4.6) - sehe

6

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

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

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

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

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

2
关于它不能被多次调用的观点很好。 - Johannes Schaub - litb
这是不直接提供它的一个很好的理由。 - AProgrammer

2

当我在谷歌上搜索“rvalue的reference_wrapper”时,偶然间看到了这个问题。不确定我的答案是否有用,它与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&&) = delete; - Mister_Jesus
@MrBin 对不起,我不确定我理解了你的问题。合法做什么? - Taras
:_pointer(::std::addressof(ref_value)) - ref_value 可以是左值或右值类型。对于右值,std::addressof 被删除。 - Mister_Jesus
@MrBin 噢。是的,这是合法的,因为如果一个右值引用被绑定到一个命名标识符(在这种情况下是“ref_value”),它本身就是一个左值。因此,它的地址可以被获取。顺便说一句,这就是为什么我们必须对通过右值引用接收的参数再次使用std::move,并且为什么我们必须使用std::forward来进行转发(“通用”)引用:以“恢复”该对象的“rvalue性”。 - Taras
我忘记了命名rvalue的规则。谢谢。 - Mister_Jesus

0

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

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

没错。但由于缺少“移动捕获”,字符串将被“复制捕获”。你甚至可以利用bind的移动捕获能力,看看我的回答。 - sellibitze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接