如何使模板右值引用参数只绑定到右值引用
How to make template rvalue reference parameter ONLY bind to rvalue reference?
我正在编写一个网络库,并大量使用move语义来处理文件描述符的所有权。我的一个类希望接收其他类型的文件描述符包装器并获得所有权,所以它类似于
struct OwnershipReceiver
{
template <typename T>
void receive_ownership(T&& t)
{
// taking file descriptor of t, and clear t
}
};
它必须处理多个不相关的类型,所以receive_ownership必须是一个模板,为了安全起见,我希望它只绑定到右值引用,这样用户在传递左值时必须显式地声明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和概念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
的函数将不接收右值,而是接收右值引用。
对于左值引用,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_V<TF>
),似乎尝试is_rvalue_reference<TF>
(其中TF
是完美转发的类型)并不奏效。
一种解决方案(尽管有点黑客)是衰减转发的T
,然后将重载放在一个知道这些类型的容器中。生成如下示例:
哈,我错了,只是忘了看Toby的答案(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
}
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 将常量指针引用绑定到非常量指针
- 运行时错误:引用绑定到类型为"int"的空指针
- 模板允许左值与右值引用绑定
- 运行时错误:引用绑定到类型"int"的未对齐地址0xbebebebebebebec6,这需要 4 个字节对齐 (stl_vector.h)
- 无法将类型"T&"的非常量左值引用绑定到类型"T"的右值 t++ std::atomic<T>
- 为什么定义复制构造函数会给我错误:无法将类型 'obj&' 的非常量左值引用绑定到类型为"obj"的右值?
- 将引用绑定到指针的语法是什么?(各种)
- 为什么我不能将常量左值引用绑定到返回 T&&&的函数?
- 为什么 VS 无法将右值引用绑定到指针?
- 出于什么原因,有必要将常量左值引用绑定到右值?
- 为什么此右值引用绑定到左值?
- 使用“void*”将右值引用绑定到左值
- 排序时引用绑定到 'value_type' 类型的 null 指针
- 无法将类型为"类名 &"的非常量左值引用绑定到类型为"类名"的右值
- 引用绑定和复制构造函数/移动构造函数
- 将引用绑定到类型的值会删除限定符 MULTISET
- 无法将类型"int&"的非常量左值引用绑定到类型为"int"的右值
- 多态变体,并将一种类型的引用绑定到另一种类型的引用
- C++17:是编译器为(静态存储持续时间)const引用绑定创建的可修改的临时对象(和存储)