C++17 折叠表达式

折叠表达式

C++17之前处理参数包

C++17折叠表达式的出现让我们不必再用递归实例化模板的方式来处理参数包。

在 C++17之前,实现必须分成两个部分:

  • 终止条件
  • 递归条件

这样的实现不仅写起来麻烦,对 C++编译器来说也很难处理。C++17折叠表达式能显著的减少程序员和编译器的工作量

// 终止条件
// - 终止条件要写在递归条件之前
template<typename First>  
First foldSum1(First&& value)  
{  return value;  
}
// 递归条件
template<typename First, typename... Rest>  
First foldSum1(First&& first, Rest&&... rest)  
{  return first + foldSum1(std::forward<Rest>(rest)...);  
}auto i1 = foldSum1(58, 25, 128, -10);  //201
auto s1 = foldSum1(std::string("a "), std::string("b "), std::string("c"));//"a b c"

C++17 折叠表达式 ... op

用途

自从 C++17起,新特性 折叠表达式 可以计算对参数包中的所有参数应用一个二元运算符的结果。

**目的:**不必再用递归实例化模板的方式来处理参数包(简化对C++11中引入的参数包的处理)

可以使代码更简洁、更易于阅读,但也可能会使代码更难以理解。需要谨慎使用,并考虑是否有其他更直观的方法可以达到相同的效果。

template<typename... T>
auto foldSum2(T... args)
{// 一元 无初值,不允许传递空参数包//  - 左折叠return (... + args); // ((arg1 + arg2) + arg3) ...//  - 右折叠return (args + ...); // ((arg3 + arg2) + arg1) ...// 二元 有初值,允许传递空参数包//  - 左折叠return (0 + ... + args); // 0 + ((arg1 + arg2) + arg3) ...//  - 右折叠return (args + ... + 0); // ((arg3 + arg2) + arg1) ... + 0
}
auto i2 = foldSum2(58, 25, 128, -10);  //201
auto s2 = foldSum2();//"a b c"

展开规则

折叠表达式共有四种语法形式:一元 二元,左折叠 右折叠 的两两组合。

  • 一元 二元:

    • 不存在/存在 初值

    • 对空参数包处理不同(二元一定允许,一元未必允许。见下文)

  • 左折叠 右折叠:

    • 初始值在 左边/右边
    • 表达式求值顺序,从 左边/右边 开始——展开之后从左边/右边开始折叠

示例中符号的含义如下:

  • op:运算符
  • args:参数包,含有未展开的形参包
  • value:初始值,不含未展开的形参包

注意事项

  • 优先选,左折叠表达式 (... + args);(value op ... op args);,因为对大多数人来说从左向右求值更符合直觉。
  • ()开闭括号也是折叠表达式的一部分,不能被省略
  • argsvalue不含优先级低于 转型表达式 的运算符的表达式。
# 1.一元左折叠(unary left fold)
# - 展开前
( ... op args )
# - 展开后
((arg_1 op arg_2) op ...) op arg_N
((1 + 2) + ...) + 9# 2.二元左折叠(binary left fold)
# - 展开前
(value op ... op args)
# - 展开后
(((arg_value op arg_1) op arg_2) op ...) op arg_N
(((0 + 1) + 2) + ...) + 9# 3.一元右折叠(unary right fold)
# - 展开前
(args op ...)
# - 展开后
arg_1 op (... op (arg_N-1 op arg_N))
1 + ( ... + (8 + 9))# 4.二元右折叠(binary right fold)
# - 展开前
(args op ... op value)
# - 展开后
arg_1 op (... op (arg_N−1 op (arg_N op arg_value)))
1 + ( ... + (8 + (9 + 0))

sizeof…

template <class ...Ts>
constexpr auto get_common_type(Ts ...ts) {if constexpr (sizeof...(Ts) == 0) {}if constexpr (sizeof...(ts) == 0) {

处理空参数包

一元折叠

将一元折叠用于长度为零的包展开时:

运算符空包的值
逻辑与 &&true
逻辑或 `
逗号运算符,void()
所有其他的运算符格式错误,编译失败
template<typename... Args>
bool all(Args... args) { return (... && args); }
template<typename... Args>
bool any(Args... args) {return  (... || args);}bool b = all(true, true, true, false);
// 在 all() 中,一元左折叠展开成
//  return ((true && true) && true) && false;
// b 是 false
二元折叠

一定允许空参数包。

合法的操作符 opt

二元运算符

二元运算符:合法的有32 个

+ - * / % 
^ & | = < > << >> 
+= -= *= /= %= ^= &= |= <<= >>= == != <= >= 
&& || , .* ->*

注意:默认情况下 lambda以值返回对象,这意味着会创建参数的不必要的拷贝。

解决方法:显式指明返回类型为 const auto&或者 decltype(auto):

template<typename First, typename... Args>
void print (const First& firstarg, const Args&... args) {std::cout << firstarg;auto spaceBefore = [](const auto& arg)> const auto& {std::cout << ' ';return arg;};(std::cout << ... << spaceBefore(args)) << '\n';
}template<typename First, typename... Args>
void print (const First& firstarg, const Args&... args) {std::cout << firstarg;(std::cout << ... << [] (const auto& arg)> decltype(auto) {std::cout << ' ';return arg;}(args)) << '\n';
}
二元操作符

所有的二元操作符

  • . ->[]
  • , 逗号运算符(左叠表达式可以使用)
折叠函数调用

折叠表达式可以用于逗号运算符 —— 因此可以在一条语句里进行多次函数调用。

// 对所有参数调用函数 foo()
template<typename... Types>
void callFoo(const Types&... args)
{(... , foo(args)); // 调用foo(arg1),foo(arg2),foo(arg3),...// 如果需要支持移动语义(... , foo(std::forward<Types>(args)));// 如果 foo()函数返回的类型重载了逗号运算符,那么代码行为可能会改变// 安全的做法: 把返回值转换为 void(... , (void)foo(std::forward<Types>(args))); // 调 用foo(arg1),foo(arg2),...
}

例如:组合哈希函数

template<typename T>
void hashCombine (std::size_t& seed, const T& val)
{seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}template<typename... Types>
std::size_t combinedHashValue (const Types&... args)
{std::size_t seed = 0; // 初始化seed(... , hashCombine(seed, args)); // 链式调用hashCombine()return seed;
}combinedHashValue ("Hi", "World", 42);
// hashCombine(seed, "Hi"), hashCombine(seed, "World"), hashCombine(seed, 42);

玩的骚一点,可以调用可变数量基类的成员函数

#include <iostream>
// 可变数量基类的模板 
template<typename... Bases>
class MultiBase : private Bases...
{
public:void print() {// 调用所有基类Bases...的print()函 数(... , Bases::print());}
};
struct A { void print() { std::cout << "A::print()\n"; }}
struct B { void print() { std::cout << "B::print()\n"; }}
struct C { void print() { std::cout << "C::print()\n"; }}
int main()
{MultiBase<A, B, C> mb;mb.print();// (A::print(), B::print()), C::print();
}
处理类型

配合 SFINAE 或者 concept,可以使用折叠表达式处理模板参数包。

像通常一样,运算符 &&会短路求值(出现第一个 false时就会停止运算)

例如:检查模板参数包Ts ...ts 与某一类型是否存在关系。

template <class T0, class ...Ts>
// 可以显式转换(过于宽松)
// requires ((true && ... && std::is_constructible_v<Ts, T0>))
// 可以隐式转换
requires ((true && ... && std::is_convertible_v<Ts, T0>))
// 完全相同(过于严格)
// requires ((true && ... && std::is_same_v<Ts, T0>))
std::array<T0, sizeof...(Ts) + 1> myvec(T0 t0, Ts ...ts) {return {t0, ts...};
}

例如:类型是否有任意一个可以转换

// - Ts中 是否有任意一个 可以与T转换
template <class T, class ...Ts>
struct is_convertible_any {static constexpr bool value = (false || ... || std::is_convertible_v<T, Ts>);
};

例如:类型是否有任意一个相同

// - Ts中 是否有任意一个 与T类型相同
template <class T, class ...Ts>
struct is_same_any {static constexpr bool value = (false || ... || std::is_same_v<T, Ts>);
};

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

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

相关文章

大语言模型数据流程源码解读(基于llama3模型)

文章目录 前言一、数据进入LlamaForCausalLM(LlamaPreTrainedModel)类二、数据进入LlamaModel(LlamaPreTrainedModel)类1、input_ids的embedding编码2、position_ids位置获取3、causal_mask因果mask构建1、causal_mask调用2、因果mask代码解读(_update_causal_mask)4、hidden_s…

Docker镜像的创建、修改与导出

Docker镜像的创建、修改与导出 前言一、创建Docker镜像1. 基于已有镜像创建方法一:修改现有镜像方法二:使用Dockerfile通过源码编译安装nginx二、修改Docker镜像1. 基于已有镜像创建新镜像方法一:修改现有镜像2. 使用`docker commit`命令创建新镜像方法一:提交正在运行的容…

Depcheck——专门用于检测 JavaScript 和 Node.js 项目中未使用依赖项的工具

文章目录 Depcheck 是什麽核心功能&#x1f4da;检测未使用的依赖&#x1f41b;检测缺失的依赖✨支持多种文件类型&#x1f30d;可扩展性 安装与使用1. 安装 Depcheck2. 使用 Depcheck Depcheck 的应用总结项目源码&#xff1a; Depcheck 是什麽 来看一个常见错误场景&#x1…

Sqoop的安装配置及使用

Sqoop安装前需要检查之前是否安装了Tez,否则会产生版本或依赖冲突&#xff0c;我们需要移除tez-site.xml&#xff0c;并将hadoop中的mapred-site.xml配置文件中的mapreduce驱动改回成yarn&#xff0c;然后分发到其他节点&#xff0c;hive里面配置的tez也要移除&#xff0c;然后…

sqoop抽取数据报驱动包不存在的问题

sqoop抽取数据报驱动包不存在的问题 报错示例:需要把相应的jar包放到sqoop的lib目录下: 可以正常查询

SpringBoot后端开发常用工具详细介绍——flyway数据库版本控制工具

文章目录 什么是flyway简介为什么要使用flyway 流程介绍整合springboot添加pom文件配置flyway向resource/db/migration添加sql文件 注意事项1. 迁移报错2. 迁移顺序 参考 什么是flyway 简介 为什么要使用flyway 我们在开发时往往会有这样一种情况&#xff1a; 进行软件开发…

HCIP-HarmonyOS Application Developer V1.0 笔记(一)

HarmonyOS的系统特性 硬件互助&#xff0c;资源共享;一次开发&#xff0c;多端部署;统一OS&#xff0c;弹性部署。 分布式软总线&#xff1a;分布式任务调度、分布式数据管理、分布式硬件虚拟化的基座 18N的独立设备 1个手机&#xff0c;8种设备&#xff08;车机&#xff0c…

深入解析HTTP与HTTPS的区别及实现原理

文章目录 引言HTTP协议基础HTTP响应 HTTPS协议SSL/TLS协议 总结参考资料 引言 HTTP&#xff08;HyperText Transfer Protocol&#xff09;超文本传输协议是用于从Web服务器传输超文本到本地浏览器的主要协议。随着网络安全意识的提高&#xff0c;HTTPS&#xff08;HTTP Secure…

stm32使用SIM900A模块实现MQTT对接远程服务器

SIM900A模块是一种GSM/GPRS无线通信模块,它可以通过SIM卡连接移动通信网络,并通过串口或USB接口与微控制器或计算机进行通信。 SIM900A驱动代码如下: #include "stm32f10x.h" #include "stdio.h" #include "stdlib.h" #include "sim900a…

算法:LeetCode283_移动零_Java实现

package com.leetcode;import java.util.Arrays;/*** LeetCode283&#xff1a;移动零*/ public class LeetCode283 {public static void moveZeroes(int[] nums) {//1.remove nums中0int slowIndex0;for(int fastindex0;fastindex<nums.length;fastindex){if(nums[fastindex…

Docker搭建基于Nextcloud的个人云盘/私有云盘/个人相册/家庭NAS

安装配置Docker 官方安装文档&#xff1a;https://docs.docker.com/engine/install/ Docker常用命令&#xff1a;https://blog.csdn.net/qq_43003203/article/details/139532097?spm1001.2014.3001.5502 Docker镜像仓库配置方法和国内常用镜像仓库地址&#xff1a; 输入&a…

杂项笔记

这个好像如果如果分配空间就会执行 这个扩容好像会进行拷贝 4 没懂 X x 0; X x1 {0,0}; 都会调用X::X(int x1, int x2 0)

【VUE点击父组件按钮,跳转到子组件】

要实现在Vue中&#xff0c;父组件通过点击按钮进入子组件的 <el-dialog> 弹窗&#xff0c;并在弹窗中嵌套 <el-table> 表格&#xff0c;可以按照以下步骤进行编写代码&#xff1a; 在父组件中&#xff0c;定义一个数据属性用于控制子组件弹窗的显示与隐藏。 data…

HTTP介绍及请求过程

HTTP(HyperText Transfer Protocol),即超文本传输协议,是一种用于分布式、协作式和超媒体信息系统的应用层协议。以下是关于 HTTP 的详细介绍: 一、基本概念 定义与作用: HTTP 是互联网上应用最为广泛的一种网络协议,它定义了客户端和服务器之间请求和响应的标准方式。…

Python从入门到高手7.3节-列表的常用操作方法

目录 7.3.1 列表常用操作方法 7.3.2 列表的添加 7.3.3 列表的查找 7.3.4 列表的修改 7.3.5 列表的删除 7.3.6 与列表有关的其它操作方法 7.3.7 与10月说再见 7.3.1 列表常用操作方法 列表类型是一种抽象数据类型&#xff0c;抽象数据类型定义了数据类型的操作方法。在本…

2024.10.29- Linux(CentOS7)笔记(1)

一、Linux文件系统的介绍 Linux的文件系统与Unix的文件系统类似&#xff0c;也是一个树形结构。 最基本的是根目录&#xff1a;/ 。相当于windows文件系统的盘符。 /&#xff1a; 根路径&#xff0c; 根路径下有以下文件夹 ​ /bin -> usr/bin : 存储的是用户级别的指令…

腾讯通低成本替代方案:支持银河麒麟及统信等国产系统和移动端

一、腾讯通继续使用的核心痛点 自腾讯通停更及官网下架后&#xff0c;用户获取更新、技术支持和资源下载的渠道被切断&#xff0c;面临以下无法解决的关键问题&#xff1a; ● 在国产系统及移动端无法使用&#xff1a;腾讯通仅兼容Windows与MAC系统&#xff0c;致使用户在国产…

vue路由的基本使用

vue路由的基本使用 vue-router简介一、路由配置和使用1、安装2、创建路由实例2、在组件中引用路由 router-view ,如APP根组件中直接引用&#xff1a;3、最后还需要把路由挂载到APP实例中&#xff0c;在main.js中注册路由&#xff1a; 二、路由重定向与别名三、声明式导航1、传统…

大模型低资源部署策略

文章目录 解码效率分析大模型训练后量化方法经验性分析与相关结论由于大模型的参数量巨大,在解码阶段需要占用大量的显存资源,因而在实际应用中的部署代价非常高。在本文中,我们将介绍一种常用的模型压缩方法,即模型量化(ModelQuantization),来减少大模型的显存占用,从…

Qt元对象系统 —— 信号与槽

信号与槽讨论的是Qt对象之间的连接与交互。我们就是使用这种方式实现了一个简单的异步调用。换而言之&#xff0c;信号与槽让我们可以不必考虑复杂的调用。只需要当我们需要在程序中表达&#xff1a;“希望在程序中通知一个事件而且按照我们设定的方式给出回应”的时候&#xf…