如何使模板右值引用参数仅绑定到右值引用?

时间:2011-10-23 00:42:43

标签: c++ rvalue-reference

我正在编写网络库并大量使用移动语义来处理文件描述符的所有权。我的一个类希望接收其他类型的文件描述符包装并取得所有权,所以它就像

struct OwnershipReceiver
{
  template <typename T>
  void receive_ownership(T&& t)
  {
     // taking file descriptor of t, and clear t
  }
};

它必须处理多个不相关的类型,所以receive_ownership必须是一个模板,为了安全起见,我希望它只绑定到rvalue引用,这样用户在传递左值时必须显式地声明std :: move。

receive_ownership(std::move(some_lvalue));

但问题是:C ++模板推导允许在没有额外努力的情况下传递左值。我实际上是因为不小心将左值传递给receive_ownership并且稍后使用了左值(清除)而将自己击中了一脚。

所以这就是问题:如何使模板仅绑定到右值参考?

5 个答案:

答案 0 :(得分:31)

您可以将T限制为不是左值引用,从而防止左值绑定到它:

#include <type_traits>

struct OwnershipReceiver
{
  template <typename T,
            class = typename std::enable_if
            <
                !std::is_lvalue_reference<T>::value
            >::type
           >
  void receive_ownership(T&& t)
  {
     // taking file descriptor of t, and clear t
  }
};

T添加某种限制以使其仅接受文件描述符包装器也可能是个好主意。

答案 1 :(得分:8)

一种简单的方法是提供一个接受左值引用的已删除成员

template<typename T> void receive_ownership(T&) = delete;

这对于左值参数总是更好的匹配。

如果你有一个带有几个参数的函数,所有这些都需要是rvalues,我们需要几个已删除的函数。在这种情况下,我们可能更喜欢使用SFINAE来隐藏任何左值参数的函数。

这样做的一种方法可能是使用C ++ 17和Concepts TS:

#include <type_traits>

template<typename T>
void receive_ownership(T&& t)
    requires !std::is_lvalue_reference<T>::value
{
     // taking file descriptor of t, and clear t
}

#include <type_traits>

void receive_ownership(auto&& t)
    requires std::is_rvalue_reference<decltype(t)>::value
{
     // taking file descriptor of t, and clear t
}

稍微进一步,您可以定义自己的新概念,如果您想重复使用它,或者只是为了更加清晰,可能会有用:

#include <type_traits>

template<typename T>
concept bool rvalue = std::is_rvalue_reference<T&&>::value;


void receive_ownership(rvalue&& t)
{
     // taking file descriptor of t, and clear t
}

注意:使用GCC 6.1,您需要将-fconcepts传递给编译器,因为它是C ++ 17的扩展而不是它的核心部分。

为了完整起见,这是我的简单测试:

#include <utility>
int main()
{
    int a = 0;
    receive_ownership(a);       // error
    receive_ownership(std::move(a)); // okay

    const int b = 0;
    receive_ownership(b);       // error
    receive_ownership(std::move(b)); // allowed - but unwise
}

答案 2 :(得分:4)

我学到的东西似乎经常让人感到困惑:使用SFINAE是可以的,但我不能使用:

std::is_rvalue_reference<T>::value

它按照我想要的唯一方式是

!std::is_lvalue_reference<T>::value

原因是:我需要我的功能才能收到 rvalue ,而不是 rvalue 参考 。有条件地使用std::is_rvalue_reference<T>::value启用的函数将不会收到右值,而是接收右值引用。

答案 3 :(得分:2)

对于左值引用,T被推导为左值引用,对于右值引用,T被推断为非引用。

因此,如果函数绑定到右值引用,编译器最后在某个类型T中看到的是:

std::is_rvalue_reference<T>::value

而不是

std::is_rvalue_reference<T&&>::value

答案 4 :(得分:0)

不幸的是,似乎尝试is_rvalue_reference<TF>(其中TF是完美转发的类型)如果您实际上尝试进行区分{{1}的重载,则效果不佳}和const T&(例如在两者中使用T&&,一个使用enable_if,另一个使用is_rvalue_reference_v<TF>)。

一个解决方案(虽然很容易)是衰减转发的!is_rvalue_reference_V<TF>,然后将重载放在一个知道这些类型的容器中。生成this example

Hup,我错了,只是忘了看托比的回答(T) - 虽然你可以做is_rvalue_reference<TF&&>令人困惑,但我猜这就是为什么std::forward<TF>(...)也有效的原因

Anywho,这是我用于调试的内容:(1)使用decltype(arg)重载,(2)使用错误的struct检查,以及(3)正确检查:

is_rvalue_reference