【标准库的典型内容】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&),和之前的全局函数类似。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AlmaLinux 安裝JDK8

在 AlmaLinux 上安装 JDK 8 可以通过包管理器 dnf 来完成。AlmaLinux 是基于 RHEL 的一个开源发行版&#xff0c;因此其包管理系统和 RHEL 类似。以下是详细的步骤来安装 OpenJDK 8 1. 更新系统包列表 sudo dnf update -y 2. 安装 OpenJDK 8 使用 dnf 安装 OpenJDK 8。你可…

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

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

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

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

[深度学习]Pytorch框架

1 深度学习简介 应用领域:语音交互、文本处理、计算机视觉、深度学习、人机交互、知识图谱、分析处理、问题求解2 发展历史 1956年人工智能元年2016年国内开始关注深度学习2017年出现Transformer框架2018年Bert和GPT出现2022年,chatGPT出现,进入AIGC发展阶段3 PyTorch框架简…

Vue3(二)计算属性Computed,监视属性watch,watchEffect,标签的ref属性,propos属性,生命周期,自定义hook

文章目录 一 、计算属性1. 简写2. 完整写法 二、监视watch1. 监视【ref】定义的【基本类型】数据2. 监视【ref】定义的【对象类型】数据3. 监视【reactive】定义的【对象类型】数据4. 监视【ref】或【reactive】定义的【对象类型】数据中的某个属性5. 监视多个数据总结 三、wat…

html+css(如何用css做出京东页面,静态版)

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>京东</title><link rel"stylesheet&q…

基于Es和智普AI实现的语义检索

1、什么是语义检索 语义检索是一种利用自然语言处理&#xff08;NLP&#xff09;和人工智能&#xff08;AI&#xff09;技术来理解搜索查询的语义&#xff0c;以提供更准确和相关搜索结果的搜索技术&#xff0c;语义检索是一项突破性的技术&#xff0c;旨在通过深入理解单词和…

知识库管理系统的未来趋势:从单一平台到生态系统

在数字化浪潮的推动下&#xff0c;知识库管理系统&#xff08;Knowledge Base Management System, KBMS&#xff09;正逐步从传统的单一平台向更加开放、灵活、智能的生态系统转变。这一转变不仅体现了技术进步的必然结果&#xff0c;也深刻反映了市场需求的变化。本文将分析随…

neo4j节点关联路径的表示、节点的增删改查

目录 核心概念节点的增删改查&#xff08;1&#xff09;增&#xff08;2&#xff09;查&#xff08;3&#xff09;删&#xff08;4&#xff09;改 neo4j文档&#xff1a;https://neo4j.com/docs/ https://neo4j.com/docs/cypher-manual/current/introduction/ 核心概念 节点 ne…

如何将Excel表格嵌入Web网页在线预览、编辑并保存到自己服务器上?

猿大师办公助手作为一款专业级的网页编辑Office方案&#xff0c;不仅可以把微软Office、金山WPS和永中Office的Word文档内嵌到浏览器网页中实现在线预览、编辑保存等操作&#xff0c;还可以把微软Office、金山WPS和永中Office的Excel表格实现网页中在线预览、编辑并保存到服务器…

C++的哲学思想

C的哲学思想 文章目录 C的哲学思想&#x1f4a1;前言&#x1f4a1;C的哲学思想☁️C底层不应该基于任何其他语言&#xff08;汇编语言除外&#xff09;☁️只为使用的东西付费&#xff08;不需要为没有使用到的语言特性付费&#xff09;☁️以低成本提供高级抽象&#xff08;更…

在云渲染中3D工程文件安全性怎么样?

在云渲染中&#xff0c;3D工程文件的安全性是用户最关心的问题之一。随着企业对数据保护意识的增强&#xff0c;云渲染平台采取了严格的安全措施和加密技术&#xff0c;以确保用户数据的安全性和隐私性。 云渲染平台为了保障用户数据的安全&#xff0c;采取了多层次的安全措施。…

【VUE3.0】动手做一套像素风的前端UI组件库---Button

目录 引言做之前先仔细看看UI设计稿解读一下都有哪些元素&#xff1a;素材补充 代码编写1. 按钮四周边框2. 默认状态下按钮颜色立体效果3. 鼠标移入聚焦4. 模拟鼠标点击效果 组件封装1. 按类型设置颜色2. 设置按钮禁用状态3. 处理一个bug4. 看下整体组件效果5. 组件完整代码6. …

vue.js 展示一个树形结构的数据视图,并禁用其中默认选中的节点

功能描述 展示树形结构&#xff1a; 使用 Element UI 的 <el-tree> 组件展示树形结构数据。数据由 content 数组提供&#xff0c;树形结构包含了嵌套的节点及其子节点。 默认选中节点&#xff1a; 使用 defaultCheckedKeys 属性指定默认选中的节点。这些节点在树形结构渲…

求职Leetcode题目(11)

1.最长连续序列 解题思路: 方法一&#xff1a; • 首先对数组进行排序&#xff0c;这样我们可以直接比较相邻的元素是否连续。• 使用一个变量 cur_cnt 来记录当前的连续序列长度。• 遍历排序后的数组&#xff1a; 如果当前元素与前一个元素相等&#xff0c;则跳过&#xf…

Debian安装mysql遇到的问题解决及yum源配置

文章目录 一、安装mysql遇到的问题解决二、Debain系统mysql8.0的安装以及远程连接三、彻底卸载软件四、Python 操作 mysql五、debian软件源source.list文件格式说明1. 第一部分2. 第二部分3. 第三部分4. 第四部分5. 关于源的混用问题6. 按需修改自己的sources.list7. 更新软件包…

python爬虫案例——腾讯网新闻标题(异步加载网站数据抓取,post请求)(6)

文章目录 前言1、任务目标2、抓取流程2.1 分析网页2.2 编写代码2.3 思路分析前言 本篇案例主要讲解异步加载网站如何分析网页接口,以及如何观察post请求URL的参数,网站数据并不难抓取,主要是将要抓取的数据接口分析清楚,才能根据需求编写想要的代码。 1、任务目标 目标网…

LabVIEW提高开发效率技巧----使用LabVIEW工具

LabVIEW为开发者提供了多种工具和功能&#xff0c;不仅提高工作效率&#xff0c;还能确保项目的质量和可维护性。以下详细介绍几种关键工具&#xff0c;并结合实际案例说明它们的应用。 1. VI Analyzer&#xff1a;自动检查代码质量 VI Analyzer 是LabVIEW提供的一款强大的工…

Java — LeetCode 面试经典150题(一)

双指针 125.验证回文串 题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回…

验收测试:从需求到交付的全程把控!

在软件开发过程中&#xff0c;验收测试是一个至关重要的环节。它不仅是对软件质量的把关&#xff0c;也是对整个项目周期的全程把控。从需求分析到最终的软件交付&#xff0c;验收测试都需要严格进行&#xff0c;以确保软件能够符合预期的质量和性能要求。 一、需求分析阶段 在…