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

Ral*_*ang 32 c++ rvalue-reference

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

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

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

receive_ownership(std::move(some_lvalue));

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

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

How*_*ant 34

您可以限制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
  }
};
Run Code Online (Sandbox Code Playgroud)

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

  • @Ralph:您尝试过`is_rvalue_reference <T &&> :: value`吗?(注意`&&`) (5认同)
  • @fredoverflow,那么,应该使用 `!std::is_lvalue_reference&lt;T&gt;::value` 或 `std::is_rvalue_reference&lt;T&amp;&amp;&gt;::value` 呢?(第二个似乎更优雅,因为它告诉你“t”到底是什么。 (3认同)
  • @alfC现在在/sf/ask/3763115751/上给出了很好的解释 (2认同)

Tob*_*ght 11

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

template<typename T> void receive_ownership(T&) = delete;
Run Code Online (Sandbox Code Playgroud)

这将始终是lvalue参数的更好匹配.


如果你有一个带有多个参数的函数,所有这些参数都需要是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
}
Run Code Online (Sandbox Code Playgroud)

要么

#include <type_traits>

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

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

#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
}
Run Code Online (Sandbox Code Playgroud)

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

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

#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
}
Run Code Online (Sandbox Code Playgroud)

  • 我不能`template &lt;typename T&gt; void receive_ownership(T&t)= delete`吗? (2认同)

Ral*_*ang 5

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

std::is_rvalue_reference<T>::value
Run Code Online (Sandbox Code Playgroud)

我想要的唯一方法是

!std::is_lvalue_reference<T>::value
Run Code Online (Sandbox Code Playgroud)

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