繁体   English   中英

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

[英]How to make template rvalue reference parameter ONLY bind to rvalue reference?

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

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

它必须处理多个不相关的类型,因此 receive_ownership 必须是一个模板,并且为了安全起见,我希望它只绑定到右值引用,以便用户在传递左值时必须显式地 state std::move。

receive_ownership(std::move(some_lvalue));

但问题是:C++ 模板推导允许传入一个左值而不需要额外的努力。 实际上,我不小心将一个左值传递给了receive_ownership,并在以后使用该左值(清除),实际上是我自己开枪打死了自己。

所以这里有一个问题:如何使模板只绑定到右值引用?

您可以将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添加某种限制,使其仅接受文件描述符包装,这也是一个好主意。

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

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

对于左值参数,这将始终是更好的匹配。


如果您的函数需要几个参数,而所有参数都必须是右值,那么我们将需要删除几个函数。 在这种情况下,我们可能更喜欢使用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
}

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

std::is_rvalue_reference<T>::value

我想要的唯一方法是

!std::is_lvalue_reference<T>::value

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

对于左值引用,T推导为左值引用,对于右值引用,T推导为非参考。

因此,如果函数绑定到右值引用,则对于特定类型T,编译器最后看到的是:

std::is_rvalue_reference<T>::value

并不是

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

不幸的是,如果您实际上试图进行区分const T&T&&重载(例如,在两者中都使用enable_if ,其中一个与is_rvalue_reference_v<TF>一起使用),那么尝试is_rvalue_reference<TF> (其中TF是完美转发的类型)似乎效果is_rvalue_reference_v<TF> ,另一个带有!is_rvalue_reference_V<TF> )。

一个解决方案(尽管很hacky)是衰减转发的T ,然后将重载放置在知道这些类型的容器中。 产生了这个例子

嘿,我错了,只是忘了看托比的答案( is_rvalue_reference<TF&&> )-尽管您可以执行std::forward<TF>(...)令人困惑,但是我想这就是为什么decltype(arg)也可以。

不管怎么说,这是我用于调试的内容:(1)使用struct重载,(2)对is_rvalue_reference使用错误的检查,以及(3)正确的检查:

/*
Output:

const T& (struct)
const T& (sfinae)
const T& (sfinae bad)
---
const T& (struct)
const T& (sfinae)
const T& (sfinae bad)
---
T&& (struct)
T&& (sfinae)
const T& (sfinae bad)
---
T&& (struct)
T&& (sfinae)
const T& (sfinae bad)
---
*/

#include <iostream>
#include <type_traits>

using namespace std;

struct Value {};

template <typename T>
struct greedy_struct {
  static void run(const T&) {
    cout << "const T& (struct)" << endl;
  }
  static void run(T&&) {
    cout << "T&& (struct)" << endl;
  }
};

// Per Toby's answer.
template <typename T>
void greedy_sfinae(const T&) {
  cout << "const T& (sfinae)" << endl;
}

template <
    typename T,
    typename = std::enable_if_t<std::is_rvalue_reference<T&&>::value>>
void greedy_sfinae(T&&) {
  cout << "T&& (sfinae)" << endl;
}

// Bad.
template <typename T>
void greedy_sfinae_bad(const T&) {
  cout << "const T& (sfinae bad)" << endl;
}

template <
    typename T,
    typename = std::enable_if_t<std::is_rvalue_reference<T>::value>>
void greedy_sfinae_bad(T&&) {
  cout << "T&& (sfinae bad)" << endl;
}

template <typename TF>
void greedy(TF&& value) {
  using T = std::decay_t<TF>;
  greedy_struct<T>::run(std::forward<TF>(value));
  greedy_sfinae(std::forward<TF>(value));
  greedy_sfinae_bad(std::forward<TF>(value));
  cout << "---" << endl;
}

int main() {
  Value x;
  const Value y;

  greedy(x);
  greedy(y);
  greedy(Value{});
  greedy(std::move(x));

  return 0;
}

使用更现代的 C++,我们可以简单地require T&&是一个右值引用:

#include <type_traits>

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

简单演示:

#include <string>
#include <utility>
int main()
{
    auto a = std::string{};
    auto const b = a;

    receive_ownership(a);            // ERROR
    receive_ownership(std::move(a)); // okay

    receive_ownership(b);            // ERROR
    receive_ownership(std::move(b)); // okay - but unwise!

    receive_ownership(std::string{}); // okay
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM