C++惯用法: 通过std::decltype来SFINAE掉表达式

目录

1.什么是SFINAE

2.SFINAE(替换失败不是错误)

3.通过std::decltype来SFINAE掉表达式


1.什么是SFINAE

        SFINAE 技术,即匹配失败不是错误,英文Substitution Failure Is Not An Error,其作用是当我们在进行模板特化的时候,会去选择那个正确的模板,避免失败。

        SFINAE一般用于函数重载和编译期间类型检查,标准库中很多type traits模板就是通SFINAE来实现的。

        看个具体的例子:

#include <iostream>
#include <type_traits>
using namespace std;template<typename T>
struct check_has_member_id
{// 仅当T是一个类类型时,“U::*”才是存在的,从而这个泛型函数的实例化才是可行的// 否则,就将触发SFINAEtemplate<typename U>static void check(decltype(&U::id)){}// // 仅当触发SFINAE时,编译器才会“被迫”选择这个版本template<typename U>static int check(...){}enum {value = std::is_void<decltype(check<T>(NULL))>::value};
};struct TEST_STRUCT
{int rid;
};struct TEST_STRUCT2
{int id;
};int main()
{check_has_member_id<TEST_STRUCT> t1;cout << t1.value << endl;check_has_member_id<TEST_STRUCT2> t2;cout << t2.value << endl;check_has_member_id<int> t3;cout << t3.value << endl;return 0;
}
// g++ --std=c++11  xxx.c

        核心的代码是在实例化check_has_member_id对象的时候,通过模板参数T的类型,决定了结构体中对象value的值。而value的值是通过check<T>函数的返回值是否是void决定的。如果T中含有id成员的话,那么就会匹配第一个实例,返回void;如果不包含id的话,会匹配默认的实例,返回int。

        利用这个机制还可以做很多类似的判断,比如判断一个类是否是结构体。

#include <iostream>
#include <type_traits>// 2. 判断变量是否是一个struct 或者 类
// https://www.jianshu.com/p/d09373b83f86
template <typename T>
struct check
{template <typename U>static void check_class(int U::*) {}template <typename U>static int check_class(...) {}enum { value = std::is_void<decltype(check_class<T>(0))>::value };
};class myclass {};int main()
{check<myclass> t;std::cout << t.value << std::endl;check<int> t2;std::cout << t2.value << std::endl;return 0;
}

std::is_void的用法可参考:

C++17之std::void_t-CSDN博客

2.SFINAE(替换失败不是错误)

        在一个函数调用的备选方案中包含函数模板时,编译器首先要决定应该将什么样的模板参数 用于各种模板方案,然后用这些参数替换函数模板的参数列表以及返回类型,最后评估替换 后的函数模板和这个调用的匹配情况(就像常规函数一样)。

        但是这一替换过程可能会遇到问题:替换产生的结果可能没有意义。不过这一类型的替换不会导致错误,C++语言规则要 求忽略掉这一类型的替换结果。

        考虑如下的例子:

// number of elements in a raw array:
template<typename T, unsigned N>
std::size_t len (T(&)[N])
{return N;
}
// number of elements for a type having size_type:
template<typename T>
typename T::size_type len (T const& t)
{return t.size();
}

当传递的参数是裸数组或者字符串常量时,只有那个为裸数组定义的函数模板能够匹配:

int a[10];
std::cout << len(a); // OK: only len() for array matches
std::cout << len("tmp"); //OK: only len() 

        如果只是从函数签名来看的话,对第二个函数模板也可以分别用 int[10]和 char const [4]替换 类型参数 T,但是这种替换在处理返回类型 T::size_type 时会导致错误。因此对于这两个调用, 第二个函数模板会被忽略掉。

        如果传递的是裸指针,以上两个模板都不会被匹配上(但是不会因此而报错)。此时编译 期会抱怨说没有发现合适的 len()函数:

int* p;
std::cout << len(p); // ERROR: no matching len() function found

但是这和传递一个有 size_type 成员但是没有 size()成员函数的情况不一样。比如如果传递的参数是 std::allocator<>:

std::allocator<int> x;
std::cout << len(x); // ERROR: len() function found, but can’t size()

此时编译器会匹配到第二个函数模板。因此不会报错说没有发现合适的 len()函数,而是会 报一个编译期错误说对 std::allocator而言 size()是一个无效调用。此时第二个模板函数不 会被忽略掉。

如果忽略掉那些在替换之后返回值类型为无效的备选项,那么编译器会选择另外一个参数类 型匹配相差的备选项。比如: 

// number of elements in a raw array:
template<typename T, unsigned N>
std::size_t len (T(&)[N])
{return N;
}
// number of elements for a type having size_type:
template<typename T>
typename T::size_type len (T const& t)
{return t.size();}
// 对所有类型的应急选项:
std::size_t len (…)
{return 0;
}

        此处额外提供了一个通用函数 len(),它总会匹配所有的调用,但是其匹配情况也总是所有 重载选项中最差的(通过省略号...匹配)。

        对于指针,只有应急选项能够匹配上,此时编译器不会再报缺少适用 于本次调用的 len()。不过对于 std::allocator的调用,虽然第二个和第三个函数都能匹配 上,但是第二个函数依然是最佳匹配项。因此编译器依然会报错说缺少 size()成员函数。

3.通过std::decltype来SFINAE掉表达式

        对于有些限制条件,并不总是很容易地就能找到并设计出合适的表达式来 SFINAE 掉函数模 板。

        比如,对于有 size_type 成员但是没有 size()成员函数的参数类型,我们想要保证会忽略掉函 数模板 len()。如果没有在函数声明中以某种方式要求 size()成员函数必须存在,这个函数模 板就会被选择并在实例化过程中导致错误:

template<typename T>
typename T::size_type len (T const& t)
{return t.size();
}
std::allocator<int> x;
std::cout << len(x) << ’\n’; //ERROR: len() selected, 

处理这种情况有一个常见的模式或者习惯用法:

1)通过尾置返回类型语法(函数名前用auto修饰,并在函数名后跟->,再加末尾的返回类型) 来制定返回类型。

2)使用std::decltype和逗号运算符来定义返回类型。

3)将所有必须成立的表达式放置于逗号运算符开头(表达式转换为void类型,以防逗号运算符重载)。

4)在逗号运算符末尾定义一个实际返回类型(类型为返回类型)的对象。

例如:

template<typename T>
auto len (T const& t) -> decltype( (void)(t.size()), T::size_type() )
{return t.size();
}

这里返回类型定义为:

decltype( (void)(t.size()), T::size_type() )

        由于decltype构造的操作数是以逗号分隔的表达式列表,因此,最后一个表达式T::size_type()生成所需返回类型的值(decltype将其转换为返回类型)。(最后一个)逗号之前的表达式是必须成立的。在本例中就是t.size()。将表达式强制转换为void,是为了避免由于用户自定义重载表达式对于类型的逗号运算符而带来的问题。

        请注意,decltype的实参是一个未求值的操作数。这意味着可以在不调用构造函数的情况下创建"虚对象",请参考:

C/C++中decltype关键字用法总结_c++ decltype用法-CSDN博客

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

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

相关文章

嵌入式c语言——指针加修饰符

指针变量可以用修饰符来修饰

量化交易常用名词介绍(七)——模块篇

目录 七、模块篇 1. NumPy 2. pandas 3. matplotlib 4. scikit-learn 5. TensorFlow 6. TA-Lib 7. statsmodels 8. Backtrader 9. PyPortfolioOpt 10. Zipline 七、模块篇 在量化交易中&#xff0c;Python 及其丰富的库生态系统提供了强大的支持。以…

Redis部署和基础命令

一、Redis基本概念 1.1 Redis简介 Redis&#xff08;远程字典服务器&#xff09; 是一个开源的、使用 C 语言编写的 NoSQL 数据库。 Redis 基于内存运行并支持持久化&#xff0c;采用key-value&#xff08;键值对&#xff09;的存储形式&#xff0c;是目前分布式架构中不可或…

python爬虫之scrapy基于管道持久化存储操作

python爬虫之scrapy基于管道持久化存储操作 本文基于python爬虫之基于终端指令的持久化存储和python爬虫之数据解析操作而写 scrapy持久化存储 基于管道&#xff1a; 编码流程&#xff1a; 1、数据解析 2、在item类中定义相关属性 3、将解析的数据封装存储到item类型的对象 4、…

Linux内核 -- 内存管理之scatterlist结构使用

Linux Kernel Scatterlist 使用指南 1. 简介 scatterlist 结构在 Linux 内核中主要用于 DMA&#xff08;直接内存访问&#xff09;操作中的内存管理。它允许将不连续的物理内存片段表示为一个逻辑上的连续块&#xff0c;从而使 DMA 操作可以高效地处理这些不连续的内存片段。…

【问题记录】VsCode中以管理员权限运行Powershell

问题展示 今天在尝试运行nodemon命令的时候出问题&#xff0c;显示没法识别&#xff0c;经过分析发现是管理员权限的问题&#xff0c;由于是在vscode里面进行开发&#xff0c;因此特此进行配置。 方法一 直接在vscode命令行中输入如下命令&#xff1a; Start-Process powers…

IDEA如何创建原生maven子模块

文件 -> 新建 -> 新模块 -> Maven ArcheTypeMaven ArcheType界面中的输入框介绍 名称&#xff1a;子模块的名称位置&#xff1a;子模块存放的路径名创建Git仓库&#xff1a;子模块不单独作为一个git仓库&#xff0c;无需勾选JDK&#xff1a;JDK版本号父项&#xff1a;…

Linux网络命令:网络工具socat详解

目录 一、概述 二、基本用法 1、基本语法 2、常用选项 3、获取帮助 三、用法示例 1. 监听 TCP 端口并回显接收到的数据 2. 通过 TCP 端口转发数据到 UNIX 套接字 3. 将文件内容发送到 TCP 端口&#xff1a; 4. 使用伪终端进行串行通信 5、启动一个TCP服务器 6、建…

Hi3861鸿蒙开发环境搭建

1.1 安装配置Visual Studio Code 打开Download Visual Studio Code - Mac, Linux, Windows选择下载安装Windows系统的Visual Studio Code。 下载后进行安装。Visual Studio Code安装完成后&#xff0c;通过内置的插件市场搜索并安装开发所需的插件如图所示&#xff1a; 1.2 安…

实时消息推送系统,写得太好了!

websocket 协议是在 http 协议上的一种补充协议&#xff0c;是 html5 的新特性&#xff0c;是一种持久化的协议。其实 websocket 和 http 关系并不是很大&#xff0c;不过都是属于应用层的协议&#xff0c;接下来我们就开始实战。 websocket 定时推送 本教程基于 springboot …

symbol数据类型以及应用场景

在js中,Symbol是一种基本数据类型,是在ECMAScript 6 (ES6) 中引入的新特性。表示独一无二 Symbol的定义 Symbol是不完整的构造函数&#xff0c;创建symbol对象时不需要new操作符,原因是通过 new 实例化的结果是一个 object 对象&#xff0c;而不是原始类型的 symbol。 var s…

STL--栈(stack)

stack 栈是一种只在一端(栈顶)进行数据插入(入栈)和删除(出栈)的数据结构,它满足后进先出(LIFO)的特性。 使用push(入栈)将数据放入stack,使用pop(出栈)将元素从容器中移除。 使用stack,必须包含头文件: #include<stack>在头文件中,class stack定义如下: namespace std…

Druid 连接池在很多方面表现出色,但在实际应用中也可能会遇到一些缺陷或问题。

Druid 连接池是阿里巴巴开源的一个功能强大的数据库连接池&#xff0c;它具有高性能、可靠性、可管理性、安全性和扩展性等特点。然而&#xff0c;尽管 Druid 连接池在很多方面表现出色&#xff0c;但在实际应用中也可能会遇到一些缺陷或问题。 1. **连接耗尽问题**&#xff1…

13 - matlab m_map地学绘图工具基础函数 - 介绍创建管理颜色映射的函数m_colmap和轮廓图绘制颜色条的函数m_contfbar

13 - matlab m_map地学绘图工具基础函数 - 介绍创建管理颜色映射的函数m_colmap和轮廓图绘制颜色条的函数m_contfbar 0. 引言1. 关于m_colmap2. 关于m_contfbar3. 结语 0. 引言 本篇介绍下m_map中用于创建和管理颜色映射函数&#xff08;m_colmap&#xff09;和 为轮廓图绘制颜…

基于深度学习的电影推荐系统

1 项目介绍 1.1 研究目的和意义 在电子商务日益繁荣的今天&#xff0c;精准预测商品销售数据成为商家提升运营效率、优化库存管理以及制定营销策略的关键。为此&#xff0c;开发了一个基于深度学习的商品销售数据预测系统&#xff0c;该系统利用Python编程语言与Django框架&a…

SQLite 命令行客户端 + Windows 批处理应用

SQLite 命令行客户端 Windows 批处理应用 下载 SQLite 客户端1. Bat 辅助脚本1. 执行SQL.bat执行 2. 导出Excel.bat执行效果 3. 导出HTML.bat执行效果 4. 清空-订单表.bat5. 订单表.bat 2. 测试 SQL1. 创建订单表.sql2. 插入订单表.sql3. 查询订单表.sql4. 清空订单表.sql5. 删…

Qt Qwt 图表库详解及使用

文章目录 Qt Qwt 图表库详解及使用一、Qwt 概述二、安装 Qwt1. 下载和编译 Qwt2. 在项目中使用 Qwt三、Qwt 的基本使用1. 创建一个简单的折线图2. 添加图例和自定义样式四、Qwt 的交互功能1. 启用缩放和平移2. 启用数据点选择五、Qwt 的高级特性1. 实时数据更新2. 多轴绘图六、…

nvm 管理多版本 node

1、下载 先不安装node 下载 nvm 1.1.10-setup.zip 解压&#xff1a;nvm&#xff1a;https://nvm.uihtm.com/ 新建nodejs/node、nodejs/nvm文件夹用于存放node版本和nvm安装路径 安装nvm&#xff1a;上述链接有安装教程 查看是否安装成功&#xff1a;重新打开cmd 输入 nvm nv…

Hyper-V克隆虚拟机教程分享!

方法1. 使用导出导入功能克隆Hyper-V虚拟机 导出和导入是Hyper-V服务器备份和克隆的一种比较有效的方法。使用此功能&#xff0c;您可以创建Hyper-V虚拟机模板&#xff0c;其中包括软件、VM CPU、RAM和其他设备的配置&#xff0c;这有助于在Hyper-V中快速部署多个虚拟机。 在…

深入理解基本数据结构:数组详解

引言 在计算机科学中&#xff0c;数据结构是存储、组织和管理数据的方式。数组作为最基础的数据结构之一&#xff0c;广泛应用于各种编程场景。在这篇博客中&#xff0c;我们将详细探讨数组的定义、特点、操作及其在不同编程语言中的实现。 什么是数组&#xff1f; 数组是一种…