简体   繁体   English

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

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

I'm writing a network library and use move semantics heavily to handle ownership for file descriptors.我正在编写一个网络库并大量使用移动语义来处理文件描述符的所有权。 One of my class wishes to receive file descriptor wrappers of other kinds and take ownership, so it's something like我的 class 之一希望接收其他类型的文件描述符包装器并获得所有权,所以它类似于

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

It has to deal multiple unrelated types so receive_ownership has to be a template, and to be safe, I wish it ONLY binds to rvalue references, so that user has to explicitly state std::move when passing an lvalue.它必须处理多个不相关的类型,因此 receive_ownership 必须是一个模板,并且为了安全起见,我希望它只绑定到右值引用,以便用户在传递左值时必须显式地 state std::move。

receive_ownership(std::move(some_lvalue));

But the problem is: C++ template deduction allows an lvalue to be passed in without extra effort.但问题是:C++ 模板推导允许传入一个左值而不需要额外的努力。 And I actually shot myself on the foot once by accidentally passing an lvalue to receive_ownership and use that lvalue(cleared) later.实际上,我不小心将一个左值传递给了receive_ownership,并在以后使用该左值(清除),实际上是我自己开枪打死了自己。

So here is the question: how to make a template ONLY bind to rvalue reference?所以这里有一个问题:如何使模板只绑定到右值引用?

You can restrict T to not be an lvalue reference, and thus prevent lvalues from binding to it: 您可以将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
  }
};

It might also be a good idea to add some sort of restriction to T such that it only accepts file descriptor wrappers. T添加某种限制,使其仅接受文件描述符包装,这也是一个好主意。

A simple way is to provide a deleted member which accepts an lvalue reference: 一种简单的方法是提供一个接受左值引用的已删除成员

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

This will always be a better match for an lvalue argument. 对于左值参数,这将始终是更好的匹配。


If you have a function that takes several arguments, all of which need to be rvalues, we will need several deleted functions. 如果您的函数需要几个参数,而所有参数都必须是右值,那么我们将需要删除几个函数。 In this situation, we may prefer to use SFINAE to hide the function from any lvalue arguments. 在这种情况下,我们可能更喜欢使用SFINAE对任何左值参数隐藏函数。

One way to do this could be with C++17 and the Concepts TS: 一种方法是使用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
}

or 要么

#include <type_traits>

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

Going slightly further, you're able to define a new concept of your own, which may be useful if you want to reuse it, or just for extra clarity: 再进一步,您可以定义自己的新概念,如果您想重复使用它,或者为了更加清楚起见,这可能很有用:

#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
}

Note: with GCC 6.1, you'll need to pass -fconcepts to the compiler, as it's an extension to C++17 rather than a core part of it. 注意:在GCC 6.1中,您需要将-fconcepts传递给编译器,因为它是C ++ 17的扩展,而不是其核心部分。

Just for completeness, here's my simple test: 为了完整起见,这是我的简单测试:

#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
}

I learnt something that seems to confuse people quite often: using SFINAE is OK, but I can't use: 我学到了一些似乎经常使人困惑的东西:使用SFINAE是可以的,但是我不能使用:

std::is_rvalue_reference<T>::value

The only way it works as I want is 我想要的唯一方法是

!std::is_lvalue_reference<T>::value

The reason is: I need my function to receive an rvalue , not an rvalue reference . 原因是:我需要函数接收右值 ,而不是右值引用 A function conditionally enabled with std::is_rvalue_reference<T>::value will not receive an rvalue, but rather receives an rvalue reference. 有条件启用std::is_rvalue_reference<T>::value将不会收到rvalue,而是会收到rvalue引用。

For lvalue references, T is deduced to be an lvalue reference, and for rvalue references, T is deduced to be a non-reference. 对于左值引用,T推导为左值引用,对于右值引用,T推导为非参考。

So if the function binds to a rvalue reference, what is seen at the end by the compiler for a certain type T is: 因此,如果函数绑定到右值引用,则对于特定类型T,编译器最后看到的是:

std::is_rvalue_reference<T>::value

and not 并不是

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

Unfortunately, it seems like trying out is_rvalue_reference<TF> (where TF is the perfectly-forwarded type) does not work well if you are actually trying to make overloads that distinguish between const T& and T&& (eg using enable_if in both, one with is_rvalue_reference_v<TF> and the other with !is_rvalue_reference_V<TF> ). 不幸的是,如果您实际上试图进行区分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> )。

A solution (albeit hacky) is to decay the forwarded T , then place the overloads in a container aware of these types. 一个解决方案(尽管很hacky)是衰减转发的T ,然后将重载放置在知道这些类型的容器中。 Generated this example : 产生了这个例子

Hup, I was wrong, just forgot to look at Toby's answer ( is_rvalue_reference<TF&&> ) -- though it's confusing that you can do std::forward<TF>(...) , but I guess that's why decltype(arg) also works. 嘿,我错了,只是忘了看托比的答案( is_rvalue_reference<TF&&> )-尽管您可以执行std::forward<TF>(...)令人困惑,但是我想这就是为什么decltype(arg)也可以。

Anywho, here's what I used for debugging: (1) using struct overloads, (2) using the wrong check for is_rvalue_reference , and (3) the correct check: 不管怎么说,这是我用于调试的内容:(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;
}

With more modern C++, we can simply require that T&& is an rvalue reference:使用更现代的 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
}

Simple demonstration:简单演示:

#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