文章目录
前言
根据源码看要是传入左值引用的话即便调用返回右值返回的依旧是左值。(这部分真的理解不了,我找了好久也不知道为啥如此设计。我看源码一直以为自己理解错误了,我一直以为他是可以将引用擦除掉的。我还特意以为void_t可以类型擦除(我以为void_t<T&>里可以匹配到左值然后把他擦除掉。),说来可笑,这几天一直找原因。还以为我之前学习的模板类型推导理解出现了问题导。特意把之前的又都梳理了一下。后来本地demo测了一下。无语了。他就是不能将左值的引用变成右值返回。)
void_t<_Ty&>理解狭隘了,他的目的就是看你传入的类型能否是一个引用类型。因为有些情况就是不能被引用,比如数组类型不能被引用。
一、源码
老规矩。先贴源码
template <class _Ty, class = void>
struct _Add_reference { // add reference (non-referenceable type)
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
template <class _Ty>
struct _Add_reference<_Ty, void_t<_Ty&>> { // (referenceable type)
using _Lvalue = _Ty&;
using _Rvalue = _Ty&&;
};
template <class _Ty>
struct add_lvalue_reference {
using type = typename _Add_reference<_Ty>::_Lvalue;
};
template <class _Ty>
using add_lvalue_reference_t = typename _Add_reference<_Ty>::_Lvalue;
template <class _Ty>
struct add_rvalue_reference {
using type = typename _Add_reference<_Ty>::_Rvalue;
};
template <class _Ty>
using add_rvalue_reference_t = typename _Add_reference<_Ty>::_Rvalue;
template <class _Ty>
add_rvalue_reference_t<_Ty> declval() noexcept;
二、解析
1.源码解析
//定义一个基类,注意因为使用的时void_t进行模板参数校验的,因此
//class = void这个必须要写,而且默认的必须是这个类型。
//具体使用见我其他文章,他和enable_if类似,都是利用编译器
//只要能找到一个模板实现成功就不会管其他模板的报错。
//但是void_t设计的原因,以及和ebable_if区别是啥,这个只能在后续的使用中慢慢领悟了。
//定义了两个类型。一个是左值引用,一个是右值引用。就是不做改变,保持原类型。
template <class _Ty, class = void>
struct _Add_reference { // add reference (non-referenceable type)
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
//利用特化,当输入一个参数且这个参数可以被生成时,会被调用,这也是void_t设计的目的。
//这里的void_t<_Ty&>我以为有特别的含义,但是测试发现你
//void_t<_Ty&> void_t<_Ty> void_t<_Ty&&>没有区别
//目的:当非引用和右值引用时获取左值返回左值引用,获取右值返回右值引用,当参数为左值引用时,无论获取左值还是右值都为左值引用。
//利用引用折叠
//例如
//传入参数为T
//传入int; _Lvalue=int&; _Rvalue=int &&;
//传入int&; _Lvalue=int& &; _Rvalue=int& &&;
//传入int&&; _Lvalue=int& &&; _Rvalue=int&& &&;
//&& &&只有当都是右值引用类型时才会被折叠成右值引用
template <class _Ty>
struct _Add_reference<_Ty, void_t<_Ty&>> { // (referenceable type)
using _Lvalue = _Ty&;
using _Rvalue = _Ty&&;
};
//定义一个新的类型。注意未知类型编译器无法感知需要用typename显示告诉。
template <class _Ty>
struct add_lvalue_reference {
using type = typename _Add_reference<_Ty>::_Lvalue;
};
//定义一个新的类型。
template <class _Ty>
using add_lvalue_reference_t = typename _Add_reference<_Ty>::_Lvalue;
//定义一个新的类型。注意未知类型编译器无法感知需要用typename显示告诉。
template <class _Ty>
struct add_rvalue_reference {
using type = typename _Add_reference<_Ty>::_Rvalue;
};
//定义一个新的类型。
template <class _Ty>
using add_rvalue_reference_t = typename _Add_reference<_Ty>::_Rvalue;
//定义一个新的函数。没有实现,但是返回值是个右值引用。
template <class _Ty>
add_rvalue_reference_t<_Ty> declval() noexcept;
2.测试demo
#include <iostream>
#include <type_traits>
using namespace std;
namespace xxx
{
template<class T>
struct MyStruct
{
MyStruct()
{
cout << "我是未知类型" << endl;
}
using type = T;
};
template<class T>
struct MyStruct<T&>
{
MyStruct()
{
cout << "我是左值引用类型" << endl;
}
using type = T;
};
template<class T>
struct MyStruct<T&&>
{
MyStruct()
{
cout << "我是右值引用类型" << endl;
}
using type = T;
};
template<class T>
struct MyStruct<const T>
{
MyStruct()
{
cout << "我是常量类型" << endl;
}
using type = T;
};
template<class T>
struct MyStruct<const T&>
{
MyStruct()
{
cout << "我是常量左值引用类型" << endl;
}
using type = T;
};
template<class T>
struct MyStruct<const T&&>
{
MyStruct()
{
cout << "我是常量右值引用类型" << endl;
}
using type = T;
};
template <class _Ty, class = void>
struct _Add_reference1 { // add reference (non-referenceable type)
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
template <class _Ty>
struct _Add_reference1<_Ty, void_t<_Ty>> { // (referenceable type)
_Add_reference1()
{
MyStruct<_Ty> a;
}
using _Lvalue = _Ty&;
using _Rvalue = _Ty&&;
};
template <class _Ty, class = void>
struct _Add_reference2 { // add reference (non-referenceable type)
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
template <class _Ty>
struct _Add_reference2<_Ty, void_t<_Ty&>> { // (referenceable type)
_Add_reference2()
{
MyStruct<_Ty> a;
}
using _Lvalue = _Ty&;
using _Rvalue = _Ty&&;
};
template <class _Ty, class = void>
struct _Add_reference3 { // add reference (non-referenceable type)
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
template <class _Ty>
struct _Add_reference3<_Ty, void_t<_Ty&&>> { // (referenceable type)
_Add_reference3()
{
MyStruct<_Ty> a;
}
using _Lvalue = _Ty&;
using _Rvalue = _Ty&&;
};
}
//情况1:void_t<_Ty>
void fun1()
{
//传入类型
xxx::_Add_reference1<int> n1;
//非引用类型获取左值
xxx::MyStruct<xxx::_Add_reference1<int>::_Lvalue> a;
//非引用类型获取右值
xxx::MyStruct<xxx::_Add_reference1<int>::_Rvalue> b;
//传入类型
xxx::_Add_reference1<int&> n2;
//左值引用类型获值左值
xxx::MyStruct<xxx::_Add_reference1<int&>::_Lvalue> c;
//左值引用类型获值右值
xxx::MyStruct<xxx::_Add_reference1<int&>::_Rvalue> d;
//传入类型
xxx::_Add_reference1<int&&> n3;
//右值引用类型获值左值
xxx::MyStruct<xxx::_Add_reference1<int&&>::_Lvalue> e;
//右值引用类型获值右值
xxx::MyStruct<xxx::_Add_reference1<int&&>::_Rvalue> f;
}
//情况2:void_t<_Ty&>
void fun2()
{
//传入类型
xxx::_Add_reference1<int> n1;
//非引用类型获取左值
xxx::MyStruct<xxx::_Add_reference2<int>::_Lvalue> a;
//非引用类型获取右值
xxx::MyStruct<xxx::_Add_reference2<int>::_Rvalue> b;
//传入类型
xxx::_Add_reference1<int&> n2;
//左值引用类型获值左值
xxx::MyStruct<xxx::_Add_reference2<int&>::_Lvalue> c;
//左值引用类型获值右值
xxx::MyStruct<xxx::_Add_reference2<int&>::_Rvalue> d;
//传入类型
xxx::_Add_reference1<int&&> n3;
//右值引用类型获值左值
xxx::MyStruct<xxx::_Add_reference2<int&&>::_Lvalue> e;
//右值引用类型获值右值
xxx::MyStruct<xxx::_Add_reference2<int&&>::_Rvalue> f;
}
//情况3:void_t<_Ty&&>
void fun3()
{
//传入类型
xxx::_Add_reference1<int> n1;
//非引用类型获取左值
xxx::MyStruct<xxx::_Add_reference3<int>::_Lvalue> a;
//非引用类型获取右值
xxx::MyStruct<xxx::_Add_reference3<int>::_Rvalue> b;
//传入类型
xxx::_Add_reference1<int&> n2;
//左值引用类型获值左值
xxx::MyStruct<xxx::_Add_reference3<int&>::_Lvalue> c;
//左值引用类型获值右值
xxx::MyStruct<xxx::_Add_reference3<int&>::_Rvalue> d;
//传入类型
xxx::_Add_reference1<int&&> n3;
//右值引用类型获值左值
xxx::MyStruct<xxx::_Add_reference3<int&&>::_Lvalue> e;
//右值引用类型获值右值
xxx::MyStruct<xxx::_Add_reference3<int&&>::_Rvalue> f;
}
int main()
{
//测试void_t<_Ty&>里为什么时Ty&而不是Ty也不是Ty&&
//情况1:void_t<_Ty>
fun1();
cout << "----1----" << endl;
//情况2:void_t<_Ty&>
fun2();
cout << "----2----" << endl;
//情况2:void_t<_Ty&&>
fun3();
cout << "----3----" << endl;
return 0;
}
3.declval解析
- 定义:declval,没有实现,可以让你不用关心类的定义直接生成一个类的对象。但是只能在模板里使用。
- 测试demo
decltype类型推导,可以根据输入的变量或函数,表达式推断出其最后返回的类型。
void_t可以校验参数是否可以生成类似于enable_if
declval,没有实现,可以让你不用关心类的定义直接生成一个类的对象。但是只能在模板里使用。
下面是测试demo
#include <iostream>
#include <type_traits>
using namespace std;
//注释掉默认的构造函数,表示不传参数不能生成对象。
class A
{
public:
//A() {}
A(int a) :m_a(a) {}
void print() { cout << "---" << endl; }
private:
int m_a;
};
//自定义一个declval看是不是还是可以使用,如果可以证明函数本身就没有实现。
namespace xxx
{
template <class _Ty>
add_rvalue_reference_t<_Ty> declval() noexcept;
}
namespace xxxx
{
template <class _Ty, class = void>
struct MyStruct1 : false_type{};
//利用declval去生成对象
template <class _Ty>
struct MyStruct1<_Ty, void_t<decltype(xxx::declval<_Ty>().print())>> : true_type {};
template <class _Ty, class = void>
struct MyStruct2 : false_type {};
template <class _Ty>
//利用默认的构造函数去生成构造函数。
//显然这个会失败
struct MyStruct2 < _Ty, void_t < decltype(_Ty().print()) >> : true_type{};
}
int main()
{
auto a = xxxx::MyStruct1<A>::value;
auto b = xxxx::MyStruct2<A>::value;
//declval<A>().print();
return 0;
}
//当把默认的构造函数添加以后。显然都能成功了
class A
{
public:
A() {}
A(int a) :m_a(a) {}
void print() { cout << "---" << endl; }
private:
int m_a;
};
//下述代码省略
//上述代码省略
int main()
{
auto a = xxxx::MyStruct1<A>::value;
auto b = xxxx::MyStruct2<A>::value;
//非模板使用编译失败,因为函数没有实现。
declval<A>().print();
return 0;
}
4.void_t<_Ty&>测试
#include <iostream>
using namespace std;
template <class T, class = void>
struct MyStruct
{
};
template <class T>
struct MyStruct<T, void_t<T&>>
{
};
int main()
{
MyStruct<int[10]>;
return 0;
}