IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++编译期分支选择相关技术 -> 正文阅读

[C++知识库]C++编译期分支选择相关技术


程序员写的代码里,最核心的内容之一就是根据不同的条件判断执行不同的逻辑分支,也就是所谓的if else,而分支判断又可以分为运行时和编译期两种,运行时的判断大家都很熟悉,比如对于后端服务,根据请求里字段的不同走不同的逻辑分支。另一类则是编译期就可以确定的分支选择,通常和特定的类型有关,比如对于一个object pool,因为需要重复利用对象,我们可以写一个通用的函数模板来执行clear,不同的类型有不同的clear操作,同时编译期计算在性能上也有很大帮助,运行时就不需要再进行额外的判断或计算了,这也是模板元编程的核心理念。

一、函数重载和标签分发

1.1 函数重载

函数重载大家都不陌生,其实这也是C++里最基本的最常用的编译期分支选择技术,可以有多个同名的函数,编译器会根据实参和形参的匹配情况来选择最合适的版本。下面引用Exceptional C++ Style关于函数重载的一个例子:

#include <complex>

class Calc {
public:
    double Twice(double);
private:
    int Twice(int i);
    std::complex<float> Twice(std::complex<float> c);      
};

int main()
{
    Calc c;
    c.Twice(21);  //编译出错,因为int Twice(int i) 是private的
}

以上的代码的函数重载主要有以下几个步骤:
(1)名字查找:编译器会首先寻找至少包含一个名为Twice的实体作用域,并将其放入候选实体列表。本例中,编译器的名字查找首先是从Calc的作用域中开始,编译器会查看Calc中是否至少存在一个名为Twice的成员,如果没有,就会继续依次在其基类和外围空间中查找,直到找到一个至少具有一个候选函数的作用域。这里是在Calc类内部找到三个名为Twice的候选函数。
(2)重载决议。编译器在候选的重载函数中选出唯一的最佳匹配。因为c.Twice(21)参数是21,所以最佳匹配是Twice(int)。这种是精准匹配的情况,如果没有这种精准匹配,会尝试隐式转换后可以匹配的函数,如果仍然没有,编译报错。
(3)可访问性检查。最后编译器会检查函数的可访问性,因为Twice(int)是private,不可访问从而导致编译失败,是的,会直接失败而不是退而求其次选double的版本。

通过函数重载,本质上也就是在编译期选择了不同的代码分支。

1.2 标签分发

而标签分发(tag dispatch),则是对函数重载机制的利用,和我们平常习惯的函数重载不一样的点在于,会用一个参数仅仅作为一个标签,这个标签并不参与任何代码逻辑,仅仅用于指导编译器选择合适的重载版本,这也是标签分发名称的由来,标签分发通常是为了解决参数相同但需要走不同的函数的问题

标签参数通常都是一个空类,此项技术在stl库里面也有大量使用(c++20之后可以用concept更方便地实现)。这里来看一个迭代器的例子,我们知道stl里不同容器的迭代器的类型是不一样的,对于list这种,因为底层是双链表,不能随机访问,只能每次向前向后挪一,属于bidirectional_iterator,而vector这种,因为底层是连续内存空间,因此可以直接进行指针运算跳到需要的位置,属于random_access_iterator。

我们再来看std::advance函数的实现,如果需要前进n,对于random_access_iterator,我们是可以直接+n的,而对于普通的forward_iterator_tag,只能n次单加,因此可以利用标签分发写出类似以下的实现:
在这里插入图片描述
这里属于函数模板里使用标签分发,但是本质上和普通函数的重载是一样的。第三个参数就是标签,仅仅用于选择不同的函数,这个标签可以通过std::iterator_traits从迭代器中萃取出来。有了不同tag的impl函数,我们可以使用一个统一的入口函数来调impl,从而让编译器根据标签选择合适的版本。

下面是cppreference上的一个基于迭代器做自定义算法的标签分发的例子:
在这里插入图片描述

二、模板特化

模板特化指的是除了主模板之外,针对特定的模板参数额外提供特定的实现,分为全特化合偏特化两种:
(1)全特化:指定所有的模板参数
在这里插入图片描述
在这里插入图片描述
(2)偏特化:指定部分模板参数
在这里插入图片描述
在这里插入图片描述
函数模板在选择过程也有重载决议,和函数重载类似,编译器也会从候选集中选取最合适的版本进行实例化,对于特化情况,编译器会优先选择特化程度最高的,从而让程序员能够在部分或者全部参数为特定类型的情况下提供不同的实现。

三、 SFINAE

SFINAE(Substitution Failure Is Not An Error),即替换失败不是错误,这是一条针对函数模板重载决议的规则,详细的定义是:当模板形参在替换成显式指定的类型或推导出的类型失败时,只是从重载集中丢弃这个特化,而不会直接导致编译失败。通俗地来说就是,在编译器check模板调用的过程中,尝试使用某些版本会导致failure并不会导致编译失败,只要有能够正常用的,其余的failure会被忽略,这也是模板元编程经常用到的一个重要特性。

SFINAE是个很古老的特性,c++11之前就被大量运用了,到了c++11之后,标准库提供了更多的组件来简化该特性的使用,c++ 20之后又引入了concept来更简单地实现SFINAE能做地事情,但鉴于现在20完全不普及,SFINAE仍然有比较重要的价值。

SFINAE规则在模板的各个涉及到参数的部分都能生效,主要可以分为以下三类:

3.1 类型相关

这里列两个典型的例子:
在这里插入图片描述
数组长度为0会被认为是failure,因此奇数偶数会分别使用这两个版本。 在这里插入图片描述
Int类型里没有B所以会选第二个。

3.2 表达式相关

在这里插入图片描述
这个例子里,重载版本1里需要根据二者相加的表达式来推导返回类型,但X没有重载+运算符不能相加,所以是个Failure,从而会选重载2,不然虽然2是普通函数但是由于需要隐式转换优先级不如能匹配的模板.

3.3 偏特化相关

在这里插入图片描述
主模板接收两个模板参数,类型T和默认为void的类型,特化版本是对第二个类型参数进行特化为std::void_t<T&>,如果T是void,std::void_t<T&>会failure,从而match到主模板,否则特化版本优先级高,从而实现不可引用类型返回原类型,可引用类型添加引用。

四、 if constexpr

constexpr是C++11引入的一个关键字,表明是编译器常量,实际使用中有两种用途,一个是修饰变量,一个是修饰函数返回值,修饰变量表明该变量是编译期常量,必须编译器就能确定,否则会报错。而constexpr函数如果传入的实参是编译器已知的,就会产生编译器结果,否则和没有constexpr修饰的函数没有区别。const表示readonly,constexpr则进一步限定在编译器已知。

C++17进而引入了if constexpr语法,可以方便地进行编译期分支选择,比如以下方式可以定义一个通用的取值函数,输入既支持指针,也支持原始值:
在这里插入图片描述
[参考]
1.https://en.cppreference.com/w/cpp/language/sfinae
2.https://arne-mertz.de/2016/10/tag-dispatch/
3.https://www.fluentcpp.com/2018/04/27/tag-dispatching/
4.Exceptional C++ Style
5.http://en.cppreference.com/w/cpp/language/template_specialization
6.http://en.cppreference.com/w/cpp/language/partial_specialization

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-01 20:25:11  更:2022-02-01 20:25:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:53:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码