目录
基本概念和常规范例
- std::declval是c++11新标准中出现的函数模板,没有函数体(只有声明,没有实现),无法被调用,
- 一般用于与decltype,sizeof等关键字配合来进行类型推导、占用内存空间计算等。
标准库源码
- add_rvalue_reference:是C++标准库中的类模板,它的能力是给进来一个类型,他能够返回该类型的右值引用类型。比如:
- a)给进来一个int类型,返回的就是int &&
- b)给进来一个int &类型,返回的还是int &类型。这里用到了引用折叠。
- c)给进来一个int &&类型,返回的还是int &&类型。这里依旧用到了引用折叠知识。
- std::declval的功能:返回某个类型T的右值引用,不管该类型是否有默认构造函数或者该类型是否可以创建对象。
- 返回某个类型T的右值引用 这个动作是在编译时完成的,所以很多人把std::declval也称为编译时工具。
class A { public: A(int i) //构造函数 { printf("A::A()函数执行了,this=%p\n", this); } double myfunc() //普通成员函数 { printf("A::myfunc()函数执行了,this=%p\n", this); return 12.1; } };
- 应用
using YT = decltype(std::declval<_nmsp1::A>()); //利用boost输出类型名比typeid(...).name()用法输出类型名更准确。 using boost::typeindex::type_id_with_cvr; cout << "YT=" << type_id_with_cvr<YT>().pretty_name() << endl; //显示YT的类型
- 【注意】这里不要把std::declval<_nmsp1::A>后面的圆括号对丢掉,否则代码含义就发生变化了。想象函数调用时即便没有参数也要把圆括号带着。
- 输出
YT=class A &&
获取类中成员函数返回值类型
获取类A中myfunc()返回值类型
A myaobj(1); //必须为构造函数提供参数 using boost::typeindex::type_id_with_cvr; cout << "myaobj.myfunc()的返回类型=" << type_id_with_cvr<decltype(myaobj.myfunc())>().pretty_name() << endl;
- 输出类型为 double
- 使用decltype获取类型不调用函数,也就是myfunc函数不执行。
- 缺点是这种方法需要创建A类对象,有没有什么方法不需要创建对象也能获取成员函数返回值类型?
using boost::typeindex::type_id_with_cvr; cout << "A::myfunc()的返回类型=" << type_id_with_cvr<decltype(std::declval<A>().myfunc())>().pretty_name() << endl;
- 输出
【引例】
A&& ayinobj(); //看起来是一个函数声明的语法 ayinobj(); //看起来是调用ayinobj这个函数
- 上面代码编译没有错误(即语法没问题),链接出错。
- ayinobj函数只是声明,没有定义,类似于std::declval函数。
- A&& ayinobj();该函数返回的类型是A&&,可以看成是返回了一个A&&类型的对象,这种对象就可以看成是类A对象,实现类成员函数调用。
ayinobj().myfunc();
- 结合decltype,编译链接都不会报错
A&& ayinobj(); decltype(ayinobj().myfunc()) mydblvar; //定义了一个double类型的变量mydblvar;
- decltype(ayinobj().myfunc()) :不会调用ayinobj函数,也不会调用myfunc函数,所以不会报错。
- 同理,可理解decltype(std::declval<A>().myfunc())含义
std::declval的作用
- a)从类型转换的角度来讲,将任意一个类型转换成右值引用类型。
- b)从假想创建出某类型对象的角度来说,配合decltype,令在decltype表达式中,不必经过该类型的构造函数就能使用该类型的成员函数。
- 注意,std::declval不能被调用,也不能创建任何对象。
- 但std::declval能在不创建对象的情况下,达到创建了一个该类型对象的效果或者说可以假定创建出了一个该类型对象。
std::declval为什么返回右值引用类型
自己实现类似于declval的函数
template <typename T> T mydeclval() noexcept;
- 调用
using boost::typeindex::type_id_with_cvr; cout << "mydeclval<A>()的返回类型=" << type_id_with_cvr<decltype(_mydeclval<A>() )>().pretty_name() << endl; cout << "mydeclval<A>().myfunc()的返回类型=" << type_id_with_cvr<decltype(mydeclval<A>().myfunc())>().pretty_name() << endl;
- 输出
在A类中增加析构函数,引出问题
class A { public: A(int i) //构造函数 { printf("A::A()函数执行了,this=%p\n", this); } double myfunc() //普通成员函数 { printf("A::myfunc()函数执行了,this=%p\n", this); return 12.1; } private: ~A() {} };
- 再次编译,报错
- 注释掉decltype(mydeclval<A>().myfunc())所在行代码,则编译不报错。
- 因为要遵循语义限制,mydeclval<A>().myfunc()语义上要创建一个临时对象(虽然实际上没有创建,是假象创建对象的),然后由这个假象的对象去调用myfunc函数。
- mydeclval函数返回一个原类型在这种情况下的就会产生语义错误。
- 下面代码也会报同样的错误
cout << "sizeof(mydeclval<A>())=" << sizeof(mydeclval<_nmsp1::A>()) << endl;
- 返回类型本身是不好的
- 因为返回类型本身,导致为了遵循语义限制,编译器内部创建了临时的A类对象。
- 为了绕开语义限制,在设计mydeclval函数模板时,就不要返回类型T了,可以返回T&,也可以返回T&&,这样从遵循语义限制方面来说,就不会创建临时的A类对象了。这就是返回T&或者T&&的好处。