【标准库的典型内容】std::declval

一、 d e c l v a l declval declval的基本概念和常规范例

s t d : : d e c l v a l std::declval std::declval C + + 11 C++11 C++11标准中出现的一个函数模板。这个函数模板设计的比较奇怪(没有实现,只有声明),因此无法被调用,通常是和 d e c l t y e decltye decltye s i z e o f sizeof sizeof等关键字一起使用,来进行类型推导等等。


下面是 d e c l v a l declval declval的一般源码实现:

//declval的源码形式,只有声明没有实现,一般是配合decltype使用的
template<typename T>
std::add_rvalue_reference_t<T>declval() noexcept;

这里的 s t d : : a d d _ r v a l u e _ r e f e r e n c e _ t std::add\_rvalue\_reference\_t std::add_rvalue_reference_t s t d : : a d d _ r v a l u e _ r e f e r e n c e std::add\_rvalue\_reference std::add_rvalue_reference的别名模板,用于将传入的类型加入 & & \&\& &&,下面是它的使用:


//将传入的类型加上两个&&
void Test1() {using Type1 = std::add_rvalue_reference<int>::type;std::cout << "Type1 = " << type_id_with_cvr<Type1>().name() << "\n";using Type2 = std::add_rvalue_reference<int&>::type;std::cout << "Type2 = " << type_id_with_cvr<Type2>().name() << "\n"; //折叠后还是&using Type3 = std::add_rvalue_reference<int&&>::type;std::cout << "Type3 = " << type_id_with_cvr<Type3>().name() << "\n"; //折叠后还是&&//也可以是用别名模板using Type4 = std::add_rvalue_reference_t<const int>;std::cout << "Type4 = " << type_id_with_cvr<Type4>().name() << "\n";}

一般在传入参数的时候,会发生折叠引用,如 & + & & = & \&+\&\& = \& &+&&=&,这里不多讨论。

下面是运行结果:

在这里插入图片描述

至于为什么返回右值引用,而不返回左值引用,下面将会介绍。


二、 s t d : : d e c l v a l std::declval std::declval的使用

2.1 类 A A A的实现

首先,我们存在这么一个类 A A A,用于测试:

//std::declval的简单使用
class A {
public:A(int i) {std::cout << "A::A()函数执行了,this = " << this << "\n";}double myfunc(double x = 12.1) {std::cout << "A::myfunc()函数执行了,this = " << this << "\n";return x;}
};

2.2 推导 d e c l v a l declval declval返回的类名

然后,我们可以利用 d e c l t y p e + d e c l v a l decltype+declval decltype+declval来推导 A A A的类型名,如下,注意 d e c l v a l declval declval后面需要跟着一个 ( ) () ()表示函数调用:

using YT = decltype(std::declval<A>()); //将A转为右值引用std::cout << "YT = " << type_id_with_cvr<YT>().pretty_name() << "\n";

返回的类型是 A A A & & \&\& &&进行折叠后的结果,是一个右值引用类型。

在这里插入图片描述


2.3 推导 d e c l v a l declval declval返回的函数返回值类型

在类 A A A存在成员函数,如果我们想要推导这个成员函数的返回值类型,我们需要怎么做呢?

通常情况下,我们可能这样写代码:

A tmp(1);
std::cout << "mydouble() 返回类型 = " <<type_id_with_cvr<decltype((tmp.myfunc()))>().pretty_name() << "\n";

然而,这样推导会调用这个函数,如下:

在这里插入图片描述


但是,如果我们通过 d e c l v a l declval declval来推导返回值,就不会调用这个函数,这也是 d e c l v a l declval declval的使用场景之一,并且,由于没有实例化出这个类,我们无需提供它的构造函数参数(如果存在),如下:

//如果不想调用函数而推导处函数返回值,因为decltype不会调用函数
std::cout << "mydouble() 返回类型 = " <<type_id_with_cvr<decltype(std::declval<A>().myfunc())>().pretty_name() << "\n";

可以发现,没有调用函数就推导出了其返回值类型:

在这里插入图片描述


这样的写法可以看做 d e c l v a l < A > ( ) declval<A>() declval<A>()返回一个 A & & A\&\& A&&的临时变量,然后这个临时变量调用了 m y f u n c ( ) myfunc() myfunc()函数。

可以参考下面的写法:
在这里插入图片描述
如果直接调用 a y i n o b j k ( ) ayinobjk() ayinobjk(),将会链接错误,因为这个函数没有实现。
而如果通过 d e c l t y p e decltype decltype,那么将不会编译失败,也不会链接失败:

下图的写法实际上是推导出了 d o u b l e double double类型,然后定义了 d o u b l e double double类型的变量 m y d b l v a l mydblval mydblval
在这里插入图片描述
通过这个例子,也就可以理解了 d e c l v a l < A > ( ) . m y f u n c ( ) declval<A>().myfunc() declval<A>().myfunc()的写法了。


三、 d e c l v a l declval declval返回右值的原因

3.1 返回值自身的问题

首先我们实现三种返回值的 d e c l v a l declval declval

//返回值
template<typename T>
T mydeclval() noexcept; //只声明,无法被调用//返回右值引用
template<typename T>
T&& mydeclval2() noexcept; //只声明,无法被调用//返回左值引用也行
template<typename T>
T& mydeclval3() noexcept; //只声明,无法被调用

我们类 A A A中加入一个 p r i v a t e private private的析构函数:

//std::declval的简单使用
class A {
public:A(int i) {std::cout << "A::A()函数执行了,this = " << this << "\n";}double myfunc(double x = 12.1) {std::cout << "A::myfunc()函数执行了,this = " << this << "\n";return x;}private:~A() {std::cout << "A::~A()函数执行了\n";}
};

此时,如果我们使用返回值类型的 d e c l t y p e decltype decltype语义上将会编译错误:

std::cout << "mydeclval<A>的返回类型 = " << type_id_with_cvr<decltype(mydeclval<A>())>().pretty_name() << "\n";
//无法被析构,从语义上要实例化一个临时对象A(尽管实际上并不会)
std::cout << "mydeclval<A>的返回类型 = " << type_id_with_cvr<decltype(mydeclval<A>().myfunc())>().pretty_name() << "\n";//同样的还有sizeof,也会编译失败
std::cout << "mydeclval<A>的大小 = " << sizeof(mydeclval<A>()) << "\n";

因为是返回值类型,所以会生成一个临时变量,但是这个临时变量的析构函数在 p r i v a t e private private内,无法被析构,因此编译器会报错(编译器无法生成一个不能析构的变量),即使使用 d e c l t y p e decltype decltype不会实例化出任何类型。


如果这里使用左值引用或右值引用的返回类型,就可以顺利通过编译了,因为是返回引用,所以无需考虑创建副本、析构的问题,因此在语义上是能通过编译的:


std::cout << "mydeclval<A>的返回类型 = " << type_id_with_cvr<decltype(mydeclval<A>())>().pretty_name() << "\n";
//无法被析构,从语义上要实例化一个临时对象A(尽管实际上并不会)
std::cout << "mydeclval<A>的返回类型 = " << type_id_with_cvr<decltype(mydeclval<A>().myfunc())>().pretty_name() << "\n";//同样的还有sizeof,也会编译失败
std::cout << "mydeclval<A>的大小 = " << sizeof(mydeclval<A>()) << "\n";

3.2 返回左值引用还是右值引用

我们知道,如果形参是 & \& &,那么通过折叠引用返回的类型永远都将是 & \& &,左值引用,无法得到右值引用。

运行下面的代码:

//返回左值引用还是右值引用
void Test4() {//如果是右值引用std::cout << "返回值为A&&的折叠引用情况:\n";std::cout << "decltype<mydecltype2<A>()>返回类型为:" <<type_id_with_cvr<decltype(mydeclval2<A>())>().pretty_name() << "\n";std::cout << "decltype<mydecltype2<A&>()>返回类型为:" <<type_id_with_cvr<decltype(mydeclval2<A&>())>().pretty_name() << "\n";std::cout << "decltype<mydecltype2<A&&>()>返回类型为:" <<type_id_with_cvr<decltype(mydeclval2<A&&>())>().pretty_name() << "\n";std::cout << "\n";//如果是左值引用std::cout << "返回值为A&的折叠引用情况:\n";std::cout << "decltype<mydecltype3<A>()>返回类型为:" <<type_id_with_cvr<decltype(mydeclval3<A>())>().pretty_name() << "\n";std::cout << "decltype<mydecltype3<A&>()>返回类型为:" <<type_id_with_cvr<decltype(mydeclval3<A&>())>().pretty_name() << "\n";std::cout << "decltype<mydecltype3<A&&>()>返回类型为:" <<type_id_with_cvr<decltype(mydeclval3<A&&>())>().pretty_name() << "\n";}

可见,使用右值引用可以得到两种引用情况,而使用左值引用只能得到左值引用类型:

在这里插入图片描述

3.2 调用引用限定符修饰的成员函数

通常,成员函数可以用一些限定,写在函数的 ( ) () ()之后,如 c o n s t / & / & & const/\&/\&\& const/&/&&等等,而调用它的类型也必须满足这样的限定。
我们这里具体讨论一下,参考下方代码:

//调用引用限定符修饰的成员函数
class ALR {
public:void onAnyValue() {std::cout << "ALR::onAnyValue()函数执行了\n";}void onLvalue()& { //只能被ALR的左值对象调用std::cout << "ALR::onLvalue()函数执行了\n";}void onRvalue()&& { //只能被ALR的右值对象调用std::cout << "ALR::onRvalue()函数执行了\n";}
};void Test5() {//返回右值引用decltype(mydeclval2<ALR>().onAnyValue()); //成功,没有限制//decltype(mydeclval2<ALR>().onLvalue()); //失败,&& 不能调用 &decltype(mydeclval2<ALR>().onRvalue()); //成功,&& 调用&&decltype(mydeclval2<ALR&>().onAnyValue()); //成功,没有限制decltype(mydeclval2<ALR&>().onLvalue()); //成功,&+ && = &//decltype(mydeclval2<ALR&>().onRvalue()); //失败,&+&& = &,不能调用&&decltype(mydeclval2<ALR&&>().onAnyValue()); //成功,没有限制//decltype(mydeclval2<ALR&&>().onLvalue()); //失败,&&+ && = &&,不能调用&decltype(mydeclval2<ALR&&>().onRvalue()); //成功,&&+&& = &&//返回左值引用decltype(mydeclval3<ALR>().onAnyValue()); //成功,没有限制decltype(mydeclval3<ALR>().onLvalue()); //成功,&调用&//decltype(mydeclval3<ALR>().onRvalue()); //失败,& 调用&&decltype(mydeclval3<ALR&>().onAnyValue()); //成功,没有限制decltype(mydeclval3<ALR&>().onLvalue()); //成功,&+ & = &//decltype(mydeclval3<ALR&>().onRvalue()); //失败,&+& = &,不能调用&&decltype(mydeclval3<ALR&&>().onAnyValue()); //成功,没有限制decltype(mydeclval3<ALR&&>().onLvalue()); //成功,&&+ & = &,调用&//decltype(mydeclval3<ALR&&>().onRvalue()); //失败,&+&& = &,不能调用&&}

通过折叠引用,以上注释起来的部分将会编译失败,因为限定符不符。


通过观察,我们发现如果返回左值引用,那么将无法调用右值引用限定符的成员函数,而如果返回右值引用,则没有这种情况发生。 因此, d e c l v a l declval declval返回右值引用比较合适。

四、推导函数返回值

4.1 全局函数

使用 d e c l v a l declval declval可以用于推导函数返回值,参考下方模板:

//declval推导函数返回值
int myfunc(int a, int b) {std::cout << "调用了myfunc函数\n";return a + b;
}//函数模板用于推导函数返回值
template<typename T_F, typename... U_Args>
decltype(std::declval<T_F>()(std::declval<U_Args>()...)) TestFnRtnImpl(T_F func, U_Args... args) {std::cout << "---------------begin---------------\n";std::cout << "T_F:" << type_id_with_cvr<T_F>().pretty_name() << "\n";std::cout << "---------------end---------------\n";auto rtnvalue = func(args...);return rtnvalue;
}

如果调用以下函数:

//declval推导函数返回值
int myfunc(int a, int b) {std::cout << "调用了myfunc函数\n";return a + b;
}
auto res = TestFnRtnImpl(myfunc, 1, 2);

其中,上面的 d e c l t y p e ( s t d : : d e c l v a l < T _ F > ( ) ( s t d : : d e c l v a l < U _ A r g s > ( ) . . . ) ) decltype(std::declval<T\_F>()(std::declval<U\_Args>()...)) decltype(std::declval<T_F>()(std::declval<U_Args>()...))可以看做: i n t ( ∗ & & ) ( i n t , i n t ) ( i n t & & , i n t & & ) int(*\&\&)(int,int)(int\&\&,int\&\&) int(&&)(int,int)int&&,int&&类型。

即传入的函数名是一个函数指针,返回函数指针的右值引用。然后后面的一个 d e c l v a l declval declval用于展开参数包,注意 . . . ... ...的写法。


当然,我们也能使用 a u t o + d e c l t y p e auto+decltype auto+decltype的方式推导返回类型,也是一样的:

// 使用 decltype 和 auto 推导函数的返回类型
template<typename T_F, typename... U_Args>
auto TestFnRtnImpl2(T_F func, U_Args... args) -> decltype(func(args...)) {std::cout << "---------------begin---------------\n";std::cout << "T_F:" << type_id_with_cvr<T_F>().pretty_name() << "\n";std::cout << "---------------end---------------\n";return func(args...);
}

如下所示:

在这里插入图片描述

4.2 成员函数

同样的,也可以使用 d e c l v a l declval declval来推导成员函数返回值,只不过需要实例化出一个成员(不是静态成员函数),利用到的是成员函数指针和这个对象的地址。

参考下方代码:

//函数模板用于推导成员函数返回值
template<typename T_F, typename T_Obj, typename... U_Args>
decltype((std::declval<T_Obj*>()->*std::declval<T_F>())(std::declval<U_Args>()...))
TestFnRtnImp3(T_Obj* obj, T_F func, U_Args... args) {//绑定发生了偏移std::cout << "---------------begin---------------\n";std::cout << "T_F:" << type_id_with_cvr<T_F>().pretty_name() << "\n";std::cout << "T_Obj:" << type_id_with_cvr<T_Obj>().pretty_name() << "\n";std::cout << "---------------end---------------\n";return (obj->*func)(args...);
}//使用auto+decltype推导成员函数返回值
template<typename T_F, typename T_Obj, typename... U_Args>
auto TestFnRtnImp4(T_Obj* obj, T_F func, U_Args... args) -> decltype((obj->*func)(args...)) {//绑定发生了偏移std::cout << "---------------begin---------------\n";std::cout << "T_F:" << type_id_with_cvr<T_F>().pretty_name() << "\n";std::cout << "T_Obj:" << type_id_with_cvr<T_Obj>().pretty_name() << "\n";std::cout << "---------------end---------------\n";return (obj->*func)(args...);
}

只是,有个细节需要注意的,这里发生了绑定的偏移,原本绑定成员函数指针的变量绑定上了对象地址:
在这里插入图片描述同样的, ( s t d : : d e c l v a l < T _ O b j ∗ > ( ) − > ∗ s t d : : d e c l v a l < T _ F > ( ) ) ( s t d : : d e c l v a l < U _ A r g s > ( ) . . . ) (std::declval<T\_Obj*>()->*std::declval<T\_F>())(std::declval<U\_Args>()...) (std::declval<T_Obj>()>std::declval<T_F>())(std::declval<U_Args>()...),这里可以看做是: d o u b l e ( A ∗ ) : : ( d o u b l e ) ( d o u b l e & ) double (A*)::(double)(double\&) double(A)::(double)(double&),和之前的全局函数类似。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/430588.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android15之编译Cuttlefish模拟器(二百三十一)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

OpenCV特征检测(5)检测图像中的角点函数cornerMinEigenVal()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算用于角点检测的梯度矩阵的最小特征值。 该函数类似于 cornerEigenValsAndVecs&#xff0c;但它计算并存储协方差矩阵导数的最小特征值&…

MovieLife 电影生活

MovieLife 电影生活 今天看到一个很有意思的项目&#xff1a;https://www.lampysecurity.com/post/the-infinite-audio-book “我有一个看似愚蠢的想法。通常&#xff0c;这类想法只是一闪而过&#xff0c;很少会付诸实践。但这次有所不同。假如你的生活是一部电影&#xff0c…

vi | vim基本使用

vim三模式&#xff1a;① 输入模式 ②命令模式 ③末行模式&#xff08;编辑模式&#xff09; vim四模式&#xff1a;① 输入模式 ②命令模式 ③末行模式&#xff08;编辑模式&#xff09; ④V模式 一、命令模式进入输入模式方法&#xff1a; 二、命令模式基…

影刀RPA实战:java结合影刀同步采购订单数据

1.实战目标 本次实战我们用java语言结合影刀&#xff0c;实现从自用ERP系统同步订单到旺店通中&#xff0c;在工作中&#xff0c;有时候我们的运营数据不是直接在旺店通ERP中操作&#xff0c;比如我们有自己的ERP&#xff0c;完成一些特定的内部工作后&#xff0c;再把数据同步…

Rustrover2024.2 正式发布:个人非商用免费,泰裤辣

如果这个世界本身 已经足够荒唐 那究竟什么才能算是疯狂 爱情就是这样 一旦错过了 就会有另一个人代替 我们知道 jetbrains 在今年的早些时候正式为 rust 语言发布了专用的 IDE &#xff0c;也就是 rustrover。如今 rustrover 也正式跻身为 jetbrains IDE 系列的一员猛将。…

Python | Leetcode Python题解之第424题替换后的最长重复字符

题目&#xff1a; 题解&#xff1a; class Solution:def characterReplacement(self, s: str, k: int) -> int:num [0] * 26n len(s)maxn left right 0while right < n:num[ord(s[right]) - ord("A")] 1maxn max(maxn, num[ord(s[right]) - ord("…

Linux学习笔记13---GPIO 中断实验

中断系统是一个处理器重要的组成部分&#xff0c;中断系统极大的提高了 CPU 的执行效率&#xff0c;本章会将 I.MX6U 的一个 IO 作为输入中断&#xff0c;借此来讲解如何对 I.MX6U 的中断系统进行编程。 GIC 控制器简介 1、GIC 控制器总览 I.MX6U(Cortex-A)的中断控制器…

科研绘图系列:R语言堆积图(stacked barplot)

文章目录 介绍加载R包导入数据数据预处理画图导出数据系统信息介绍 微生物堆积图是一种数据可视化工具,通常用于展示微生物群落中不同物种的相对丰度。这种图表通过将每个样本中的微生物按照其分类学等级(如门、属等)进行分类,并以不同颜色的块状图表示,每个块的大小代表…

信息安全工程师(13)网络攻击一般过程

前言 网络攻击的一般过程是一个复杂且系统化的行为&#xff0c;其目标往往在于未经授权地访问、破坏或窃取目标系统的信息。 一、侦查与信息收集阶段 开放源情报收集&#xff1a;攻击者首先会通过搜索引擎、社交媒体、论坛等公开渠道获取目标的基本信息&#xff0c;如姓名、地址…

科研小白入门工具

三、科研绘图 1.流程图绘制工具&#xff1a;powerpoint、亿图图示、visio、draw.io 2.绘制标准&#xff1a;布局合理、色彩鲜明、字体大小、矢量输出 矢量图绘制推荐流程&#xff1a;亿图图示绘制--visio--word--pdf无损放大 3.文章插图&#xff1a;excel、origin、matlab、…

Linux——虚拟机网络配置

进行虚拟机网络配置是确保虚拟机能够正常访问网络、与宿主机及其他设备进行通信的关键步骤。虚拟机网络配置允许用户根据实际需求选择合适的网络模式&#xff0c;并调整网络参数以满足特定的网络环境要求。 虚拟机常见的三种网络模式包括桥接模式、NAT模式和主机模式&#xff…

甩锅笔记:好好的服务端应用突然起不来,经定位是无法访问外网了?测试又说没改网络配置,该如何定位?

在工作中、团队协作时&#xff0c;可能遇到的问题&#xff0c;如集成测试等场景。但是作为偏前端的全栈&#xff0c;锅从天上来&#xff0c;不是你想甩就能甩&#xff0c;尤其面对测试等比较强势的团体&#xff08;bug创造者&#xff09;&#xff0c;你必须有强大的心理承受能力…

热斑黄斑光伏发电板 红外黄斑检测图像数据集内含最高温度信息 1200张,jpg格式。

热斑黄斑光伏发电板 红外黄斑检测图像数据集 内含最高温度信息 1200张&#xff0c;jpg格式。 热斑黄斑光伏发电板红外黄斑检测图像数据集介绍 数据集名称 热斑黄斑光伏发电板红外黄斑检测图像数据集&#xff08;Hot Spot and Yellow Spot Detection in Photovoltaic Panels I…

C++之STL—vector容器进阶篇

vector容器的迭代器是支持随机访问的迭代器&#xff08;动态数组&#xff09; 构造函数 赋值操作 vector容量和大小 判断是否为空 --- empty 返回元素个数 --- size 返回容器容量 --- capacity 重新指定大小 --- resize vector插入和删除 * 尾插 --- push_back * …

手势手语识别数据集,YOLOv5目标检测两个数据集,图大量,模型精度高分别为近100类

手势手语识别数据集&#xff0c;YOLOv5目标检测 两个数据集&#xff0c;图大量&#xff0c;模型精度高分别为近100类 手势手语识别数据集&#xff08;Sign Language Gesture Recognition Dataset&#xff09; 数据集概述 该数据集专为手势手语识别设计&#xff0c;包含大量的…

AOT源码解析4.1-model主体解析

1 输入数据 VOS的数据集处理操作可见数据集操作&#xff0c;这里是进行数据集提取完毕后的操作。 图2&#xff1a;如图所示&#xff0c;使用datasets提取出数据之后&#xff0c;在模型训练阶段对数据做图中操作。即&#xff1a;将batch_size大小的ref_imgs、prev_imgs&#x…

【QT】QSS基础

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;QT 目录 &#x1f449;&#x1f3fb;基本语法&#x1f449;&#x1f3fb;从⽂件加载样式表&#x1f449;&#x1f3fb;选择器伪类选择器 &#x1f449;&…

supermap iclient3d for cesium模型沿路径移动

可以直接settimeout隔一段时间直接设置位置属性&#xff0c;但是得到的结果模型不是连续的移动&#xff0c;如果想要连续的移动&#xff0c;就需要设置一个时间轴&#xff0c;然后给模型传入不同时间时的位置信息&#xff0c;然后就可以了。 开启时间轴 let start Cesium.Jul…

云安全风险优先级已失效以下是修复方法

想象一下&#xff0c;你身处一栋着火的建筑物内。每个房间的火警都响了。你站在走廊里&#xff0c;手里拿着灭火器。你必须扑灭大火。你从哪里开始呢&#xff1f; 这是许多团队在保护云中的应用程序时面临的情况。 随着云采用率的增长和云应用程序的增加&#xff0c;这些应用…