最近尝试学习decltype和declval,记录一下学习内容
1.decltype是C++11出现的新关键字,在vc中打出来有着高贵的亮蓝色,是用来形容decltype(表达式)中,表达式的类型的。
简单的
int a = 10;
decltype(a) b;
此时b被声明为与a同样的类型,也就是int。
2.declval并不是关键字,只是标准库中的一员,实际上用的时候要用std::declval来使用。而且经常和decltype一起使用来解决decltype的不足,在c++标准文档中给的列子如下:
#include
#include
struct Default { int foo() const { return 1; } };
struct NonDefault
{
NonDefault() = delete;
int foo() const { return 1; }
};
int main()
{
decltype(Default().foo()) n1 = 1; // n1 的类型是 int
// decltype(NonDefault().foo()) n2 = n1; // 错误:无默认构造函数
decltype(std::declval().foo()) n2 = n1; // n2 的类型是 int
std::cout << "n1 = " << n1 << '\n'
<< "n2 = " << n2 << '\n';
}
实际上declval在标准库的定义如下:
template
typename std::add_rvalue_reference::type declval() noexcept;
下一部分就会对这展开。
二。declval的定义
在vs2017中找到的定义和标准文档中的写法略有不同但是意义是一样的,在我的type_traits中有如下定义:
// STRUCT TEMPLATE _Add_reference
template
struct _Add_reference
{ // add reference
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
template
struct _Add_reference<_Ty, void_t<_Ty&>>
{ // add reference
using _Lvalue = _Ty&;
using _Rvalue = _Ty&&;
};
// STRUCT TEMPLATE add_lvalue_reference
template
struct add_lvalue_reference
{ // add lvalue reference
using type = typename _Add_reference<_Ty>::_Lvalue;
};
template
using add_lvalue_reference_t = typename _Add_reference<_Ty>::_Lvalue;
// STRUCT TEMPLATE add_rvalue_reference
template
struct add_rvalue_reference
{ // add rvalue reference
using type = typename _Add_reference<_Ty>::_Rvalue;
};
template
using add_rvalue_reference_t = typename _Add_reference<_Ty>::_Rvalue;
// FUNCTION TEMPLATE declval
template
add_rvalue_reference_t<_Ty> declval() noexcept;
2.1 void_t
元编程接触的不多,这里对void_t的使用卡住了,于是又查了一下这是干嘛用的。
这是被定义为
template
using void_t = void;
标准文档的解释是:将任意类型的序列映射到类型 void 的工具元函数。
是c++17中增加的功能,用他可以更方便的实现SFINAE,他就相当于void类型,自己尝试理解,打个比方就如:
template
using hahha = int;
hahha aaa;
aaa就是一个int类型,跟hahha<>模板里的内容无关,void_t也是如此,只不过是利用void_t<>来尝试进行SFINAE替换操作。
模板参数列表C++11就已经实现了,为什么到C++17才又void_t?
因为之前别名模板中未使用的参数不保证 SFINAE 且可被忽略,但是之前的版本也可以用一些技巧来实现。
这个void_t就理解完了。
2.2 一些疑惑
对_Add_reference又进行了一些尝试,
using intl = std::_Add_reference::_Lvalue;
using intr = std::_Add_reference::_Rvalue;
using intnl = std::_Add_reference::_Lvalue;
using intnr = std::_Add_reference::_Rvalue;
using intnnl = std::_Add_reference::_Lvalue;
using intnnr = std::_Add_reference::_Rvalue;
这里 int和int&& 表现出来的结果是一直的 _Lvalue是int&,_Rvalue是int&&
可是int& 确实模板匹配到了
template
struct _Add_reference
{ // add reference
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
===========================================================
2020.7.3更新 又看了c++标准手册了解到了
这些类型变换遵从折叠规则:
与直接使用 T& 的主要区别是 std::add_lvalue_reference
自己测试也是,确实void& 类型会编译失败!!
7.7更新,心中还有一个疑惑就是当类型是T& 是,模板到底是选择的
template
struct My_Add_reference
还是
template
struct My_Add_reference<_Ty, void_t<_Ty&>>
自己有想到了一个新的实验方法:
template
struct My_Add_reference
{ // add reference
using _Lvalue = double;
using _Rvalue = double;
};
template
struct My_Add_reference<_Ty, void_t<_Ty&>>
{ // add reference
using _Lvalue = _Ty & ;
using _Rvalue = _Ty && ;
};
我让主模板返回值不再是_Ty类型而是double类型,进行测试
using Cintnl = My_Add_reference::_Lvalue;
using Cintnr = My_Add_reference::_Rvalue;
using Cintnl = My_Add_reference::_Lvalue;
using Cintnr = My_Add_reference::_Rvalue;
可以发现 int& 的 lvalue和rvalue仍然是 int&,而void变成double,说明 int&仍然用的是
template
struct My_Add_reference<_Ty, void_t<_Ty&>> 模板,只不过是因为遵守了叠加规则,导致了左值右值引用都是_Ty&,
主模板的存在只是为了void类型而存在的。