C 语言中std::array的神奇用法总结

std::array是在C 11标准中增加的STL容器,它的设计目的是提供与原生数组类似的功能与性能。也正因此,使得std::array有很多与其他容器不同的特殊之处,比如:std::array的元素是直接存放在实例内部,而不是在堆上分配空间;std::array的大小必须在编译期确定;std::array的构造函数、析构函数和赋值操作符都是编译器隐式声明的……这让很s多用惯了std::vector这类容器的程序员不习惯,觉得std::array不好用。

但实际上,std::array的威力很可能被低估了。在这篇文章里,我会从各个角度介绍下std::array的用法,希望能带来一些启发。

本文的代码都在C 17环境下编译运行。当前主流的g 版本已经能支持C 17标准,但是很多版本(如gcc 7.3)的C 17特性不是默认打开的,需要手工添加编译选项-std=c 17。

自动推导数组大小

很多项目中都会有类似这样的全局数组作为配置参数:

uint32_t g_cfgPara[] = {1, 2, 5, 6, 7, 9, 3, 4};

当程序员想要使用std::array替换原生数组时,麻烦来了:

array g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4};  // 注意模板参数“8”

程序员不得不手工写出数组的大小,因为它是std::array的模板参数之一。如果这个数组很长,或者经常增删成员,对数组大小的维护工作恐怕不是那么愉快的。有人要抱怨了:std::array的声明用起来还没有原生数组方便,选它干啥?

但是,这个抱怨只该限于C 17之前, C 17带来了类模板参数推导特性, 你不再需要手工指定类模板的参数:

array g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4};  // 数组大小与成员类型自动推导

看起来很美好,但很快就会有人发现不对头:数组元素的类型是什么?还是std::uint32_t吗?


有人开始尝试只提供元素类型参数,让编译器自动推导长度,遗憾的是,它不会奏效。

array g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4};  // 编译错误

好吧,暂时看起来std::array是不能像原生数组那样声明。下面我们来解决这个问题。

用函数返回std::array

问题的解决思路是用函数模板来替代类模板——因为C 允许函数模板的部分参数自动推导——我们可以联想到std::make_pair、std::make_tuple这类辅助函数。巧的是, C 标准真的在TS v2试验版本中推出过std::make_array, 然而因为类模板参数推导的问世,这个工具函数后来被删掉了。


但显然,用户的需求还是存在的。于是在C 20中, 又新增了一个辅助函数std::to_array。

别被C 20给吓到了,这个函数的代码其实很简单,我们可以把它拿过来定义在自己的C 17代码中[1]。

template<typename R, typename P, size_t N, size_t... I>constexpr array to_array_impl(P (&a)[N], std::index_sequence) noexcept{    return { {a[I]...} };}
template<typename T, size_t N>constexpr auto to_array(T (&a)[N]) noexcept{    return to_array_impl<std::remove_cv_t, T, N>(a, std::make_index_sequence{});}
template<typename R, typename P, size_t N, size_t... I>constexpr array to_array_impl(P (&&a)[N], std::index_sequence) noexcept{    return { {move(a[I])...} };}
template<typename T, size_t N>constexpr auto to_array(T (&&a)[N]) noexcept{    return to_array_impl<std::remove_cv_t, T, N>(move(a), std::make_index_sequence{});}

细心的朋友会注意到,上面这个定义与C 20的推荐实现有所差异,这是有目的的。稍后我会解释这么干的原因。

现在让我们尝试下用新方法解决老问题:

auto g_cfgPara = to_array({1, 2, 5, 6, 7, 9, 3, 4});  // 类型不是uint32_t?

不对啊,为什么元素类型不是原来的std::uint32_t?


这是因为模板参数推导对std::initializer_list的元素拒绝隐式转换,如果你把to_array的模板参数从int改为uint32_t,会得到如下编译错误:

D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: error: no matching function for call to 'to_array(<brace-enclosed initializer list>)' auto g_cfgPara = to_array({1, 2, 5, 6, 7, 9, 3, 4});D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:34:16: note: candidate: 'template<class T, long long unsigned int N> constexpr auto to_array(T (&)[N])' constexpr auto to_array(T (&a)[N]) noexcept                ^~~~~~~~D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:34:16: note:   template argument deduction/substitution failed:D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: note:   mismatched types 'unsigned int' and 'int' auto g_cfgPara = to_array({1, 2, 5, 6, 7, 9, 3, 4});D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:46:16: note: candidate: 'template<class T, long long unsigned int N> constexpr auto to_array(T (&&)[N])' constexpr auto to_array(T (&&a)[N]) noexcept                ^~~~~~~~D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:46:16: note:   template argument deduction/substitution failed:D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: note:   mismatched types 'unsigned int' and 'int'

Hoho,有点惨是不,绕了一圈回到原点,还是不能强制指定类型。

这个时候,之前针对std::array做的修改派上用场了:我给to_array_impl增加了一个模板参数,让输入数组的元素和返回std::array的元素用不同的类型参数表示,这样就给类型转换带来了可能。为了实现转换到指定的类型,我们还需要添加两个工具函数:

template<typename R, typename P, size_t N>constexpr auto to_typed_array(P (&a)[N]) noexcept{    return to_array_impl(a, std::make_index_sequence{});}
template<typename R, typename P, size_t N>constexpr auto to_typed_array(P (&&a)[N]) noexcept{    return to_array_impl(move(a), std::make_index_sequence{});}

这两个函数和to_array的区别是:它带有3个模板参数:第一个是要返回的std::array的元素类型,后两个和to_array一样。这样我们就可以通过指定第一个参数来实现定制std::array元素类型了。

auto g_cfgPara = to_typed_array({1, 2, 5, 6, 7, 9, 3, 4});  // 自动把元素转换成uint32_t

这段代码可以编译通过和运行,但是却有类型转换的编译告警。当然,如果你胆子够大,可以在to_array_impl函数中放一个static_cast来消除告警。但是编译告警提示了我们一个不能忽视的问题:如果万一输入的数值溢出了怎么办?

auto g_a = to_typed_array({256, -1});  // 数字超出uint8_t范围

编译器还是一样的会让你编译通过和运行,g_a中的两个元素的值将分别为0和255。如果你不明白为什么这两个值和入参不一样,你该复习下整型溢出与回绕的知识了。

显然,这个方案还不完美。但我们可以继续改进。

编译期字面量数值合法性校验

首先能想到的做法是在to_array_impl函数中放入一个if判断之类的语句,对于超出目标数值范围的输入抛出异常或者做其他处理。这当然可行,但要注意的是这些工具函数是可以在运行期调用的,对于这种常用的基础函数来说,性能至关重要。一旦在里面加入了错误判断,意味着运行时的每一次调用性能都会下降。

理想的设计是:只有在编译期生成的数组才进行校验,并且报编译错误。但运行时调用函数时不要加入任何校验。

可惜的是,至少在C 20之前,没有办法指定函数只允许在编译期执行[2]。那有没有其他手段呢?

熟悉C 的人知道:C 的编译期处理大多可以用模板的trick来完成——因为模板参数一定是编译期常量。因此我们可以用模板参数来完成编译期处理——只要把数组元素全部作为模板的非类型参数就可以了。当然,这里有个问题:模板的非类型参数的类型怎么确定?正好C 17提供了auto模板参数的功能,可以派上用场:

template<typename T>constexpr void CheckIntRanges() noexcept {}  // 用于终结递归
template<typename T, auto M, auto... N>constexpr void CheckIntRanges() noexcept

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

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

相关文章

java线程池并发_线程池之外:Java并发并不像您想象的那样糟糕

java线程池并发Apache Hadoop&#xff0c;Apache Spark&#xff0c;Akka&#xff0c;Java 8流和Quasar&#xff1a; 针对Java开发人员的经典用例以及最新的并发方法 关于并发性更新概念的讨论很多&#xff0c;但是许多开发人员还没有机会将他们的想法缠住。 在本文中&#xff…

网络营销理论模型_网络营销:课堂笔记(第四章下)

网络营销产品策略(续上篇)本章知识清单三、网络品牌如何打造&#xff1f;什么是品牌目前为止&#xff0c;对品牌的含义一直没有一个统一的、权威的解释。如果从品牌的构成要素和基本功能方面来界定品牌的话&#xff0c;最具有代表性和最经典的表述当属美国市场营销协会的定义。…

ios多线程Android,iOS 关于多线程

一.进程和线程1.什么是进程进程是指在系统中正在运行的一个应用程序每个进程之间是独立的&#xff0c;每个进程均运行在其专用且受保护的内存空间内比如&#xff1a;同时打开QQ&#xff0c;Xcode&#xff0c;系统就会分别启动2个进程通过”活动监视器”可以查看Mac系统中所开启…

websockets_使用用户名/密码和Servlet安全性保护WebSockets

websocketsRFC 6455提供了WebSockets安全注意事项的完整列表。 其中一些是在协议本身中烘焙的&#xff0c;其他一些则需要更多有关如何在特定服务器上实现它们的解释。 让我们来谈谈协议本身内置的一些安全性&#xff1a; HTTP请求中的Origin头仅包含标识发起该请求的主体&…

android横向排列 间隙,Android开发消除横向排列的多个Button之间的空隙

一.问题重述摘要里描述的可能不太清楚&#xff0c;问题如下图&#xff1a;如何消除Button1和Button2之间的空隙&#xff0c;以及Button与左右边界之间的空隙&#xff1f;二.问题根源这里出现的空隙其实是Button的背景图片中的透明部分&#xff0c;如下图&#xff1a;(两个按钮被…

电脑的发展史_互联网发展史 硅谷传奇之 IBM

2节 硅谷传奇之 IBM为什么要讲IBM呢&#xff1f;互联网是因计算机而诞生的&#xff0c;互联网的发展史与电脑的发展史有很多是重叠的&#xff0c;而IBM是上世纪60年代八大电脑公司之首。在互联网席卷全球之前&#xff0c;在硅谷是以无线电、军事技术、硅晶体管而闻名的。这些东…

android汉字田字格,画一个简单的田字格

image.png上代码package com.nick.customview;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.support.annotation.Nullable;import android.sup…

xp定时关机软件_好用又免费的电脑定时工具,不用得后悔

现在利用电脑办公的人有多少&#xff0c;举个手示意下&#xff01;&#xff01;&#xff01;给电脑设置定时关机&#xff0c;可以方便我们不在电脑前完成关机操作。那么&#xff0c;如何设置定时关机呢&#xff1f;如果要取消&#xff0c;定时关机又如何取消&#xff1f;有的人…

apache camel_Apache Camel请向我解释这些端点选项的含义

apache camel在即将发布的Apache Camel 2.15中&#xff0c;我们使Camel更智能。 现在&#xff0c;它可以充当老师&#xff0c;并向您说明其配置方式以及这些选项的含义。 Camel可以做的第一课是告诉您如何配置所有端点以及这些选项的含义。 接下来我们要学习的课程是让Camel解…

android照片备份软件下载,照片备份云相册app下载-照片备份云相册下载V1.9安卓版-西西软件下载...

这款软件主要为安卓手机用户提供照片图片等备份的服务&#xff0c;通过照片备份云相册app可以轻松的为更多手机空间不够的用户清理出手机空间&#xff0c;照片图片等的是最不容易发现占用空间的&#xff0c;现在有了照片备份云相册app就能轻松让手机内存足够使用哦。照片备份云…

手机照片导入电脑步骤_如何将手机中的照片、视频快速的保存到U盘上?3分钟教你详细步骤...

手机拍照功能是越来越强大了。生活中&#xff0c;不少的朋友都喜欢拍照。拍着拍着不知不觉就存了许多的照片&#xff0c;占用很大的空间。手机照片和视频都是我们非常重要的回忆&#xff0c;许多人都不愿意删除。为了节省我们手机的空间&#xff0c;许多人都会选择将手机照片导…

筒仓计算表格_身份反模式:联邦筒仓和意大利面条身份

筒仓计算表格分析公司Quocirca的最新研究证实&#xff0c;现在许多企业的外部用户比内部用户更多&#xff1a;在欧洲&#xff0c;有58&#xff05;的企业直接与其他企业和/或消费者的用户进行交易&#xff1b; 仅在英国&#xff0c;这一数字就达到了65&#xff05;。 如果回顾历…

C/C 宏替换详解

1. 基本形式#define name replacement_text 复制代码通常情况下&#xff0c;#define 指令占一行&#xff0c;替换文本是 define 指令行尾部的所有剩余部分&#xff0c;但也可以把一个较长的宏定义分成若干行&#xff0c;这时需要在待续的行末尾加上一个反斜杠符 。宏定义也可以…

win10更新助手_快升级!win10精简版不到10G,比win7还干净流畅,无需更新!

在整个PC端操作系统中&#xff0c;最成功的得主自然要说微软。毕竟微软发布的XP、win7操作系统那可是风靡全球&#xff0c;占用的用户资源十分庞大。单单因为这两个操作系统就吸引到了足够的用户&#xff0c;整个装机下载量可真不是盖的&#xff0c;说微软是PC端的"老大&q…

openshift命令_使用命令行工具创建WildFly OpenShift应用程序

openshift命令通过使用快速入门&#xff0c;可以轻松地在OpenShift上配置WildFly的新实例。 只需单击一下&#xff0c;您就可以准备就绪&#xff01; 通常&#xff0c;OpenShift的高级用户使用命令行工具 。 但是&#xff0c;您无法使用CLI工具创建WildFly墨盒。 但现在已解决…

redis缓存原理与实现_SpringBoot整合Redis缓存,手把手教你一步一步实现

推荐学习分布式大全&#xff1a;反向代理/Redis/中间件/MySQL/消息&#xff0c;挑战阿里P7必备都是“Redis惹的祸”&#xff0c;害我差点挂在美团三面&#xff0c;真是“虚惊一场”微服务架构之春招总结&#xff1a;SpringCloud、Docker、Dubbo与SpringBoot一、基本概况为什么使…

C 实现高性能内存池

版权一、概述在 C/C 中&#xff0c;内存管理是一个非常棘手的问题&#xff0c;我们在编写一个程序的时候几乎不可避免的要遇到内存的分配逻辑&#xff0c;这时候随之而来的有这样一些问题&#xff1a;是否有足够的内存可供分配? 分配失败了怎么办? 如何管理自身的内存使用情况…

docker $PWD路径_Docker安装Jenkins+Shell脚本自动化部署项目

本文同名博客老炮说Java&#xff1a;https://www.laopaojava.com/&#xff0c;每天更新Spring/SpringMvc/SpringBoot/实战项目等文章资料SentinelNacos 是微服务环境搭建必不可少的两个组件&#xff0c;这里给大家推荐一套微服务教程&#xff1a;SpringCloud微服务电商项目教程…

flyme Android7.0 root,手把手教你如何免ROOT卸载Flyme7系统自带APP

本教程需要使用adb&#xff0c;adb的全称为Android Debug bridge&#xff0c;就是起到调试桥的作用。借助adb工具&#xff0c;我们可以管理设备或手机模拟器的状态。本教程需要使用adb&#xff0c;adb的全称为Android Debug bridge&#xff0c;就是起到调试桥的作用。借助adb工…

C语言 -- 字符串中根据特定字符(串)分割

版权C语言字符串操作函数有很多&#xff0c;这里举出需要用到的&#xff0c;其他请自行查找。1、len strlen(p) &#xff1b;//取字符串长度 原型&#xff1a;size_t strlen(const char *s); 功能&#xff1a;统计字符串string中字符的个数&#xff0c;字符串的长度在 size_t…