最近在使用constexpr的时候无意中踩了个小坑。

下面给个小示例:

#include <iostream>

constexpr int n = 10;
constexpr char *msg = "Hello, world!"; int main()
{
for (auto i = 0; i < n; ++i) {
std::cout << msg << std::endl;
}
}

constexpr应该是大家很熟悉的东西了,也是最常用的c++11新特性之一。和宏相比除了更强的类型安全之外,constexpr还带来了编译期计算。

上面的代码相当简单,我们循环输出“Hello, world!”这个字符串10次。

这么简单的代码还有讨论的必要吗?一开始我也是这么想的,然而当我们编译运行的时候却会得到下面这样的警告:

$ g++ --version

g++ (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ g++ -std=c++17 -Wall -Wextra test.cpp test.cpp:4:23: 警告: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
4 | constexpr char *msg = "Hello, world!";
| ^~~~~~~~~~~~~~~

这段信息的意思是c++不允许把字符串字面量赋值给char*。

然而对于constexpr,文档中是这么写的:

A constexpr specifier used in an object declaration implies const.

这里的object你可以理解为变量,意思是constexpr修饰的变量都会隐式添加一个const限定符。

也就是说:

// T 是任意类型
constexpr T a = xxx;
// 不考虑其他因素,在类型上等价于:
T const a = xxx;

我们这里的T实际上可以填任意类型,包括指针。这不是说明我们的指针变量有const吗?

眼尖的读者大概已经知道答案了:constexpr添加的是顶层const。

所以我们的代码实际上是这样的:

// 原本的代码
constexpr char *msg = "Hello, world!";
// 实际上的效果
char * const msg = "Hello, world!";

下面一行的msg实际上是一个指向char的指针常量,而我们可以通过它任意修改被指向的字符串(当然这是未定义行为)。指针常量意味着我们不能把这个指针重新指向其他的对象,这个const作用在指针本身上,因此叫做顶层const。

而字符串常量的类型是const char[N],在表达式里退化为const char *,这表示一个指向常量字符串的指针,这里的const的底层的,因为它作用于被指向的对象而不是我们的指针自身。

对于顶层const,赋值的时候是可以被去除的,而底层const则不行,这就是为什么编译器会弹出警告的原因了。

正确的做法也很简单,牢记constexpr不是const的等价替代品,它只会添加顶层const,不会添加底层const。

所以constexpr的字符串常量应该这样写:

constexpr const char *p = "Hello, world!";

或者你的编译环境支持c++17,我更推荐你这样写:

#include <string_view>

constexpr std::string_view msg = "Hello, world!";

使用string_view之后就不会出现上面的顶层/底层const的坑了。所以在现代c++里能不用裸指针就尽量不要用。

参考

https://stackoverflow.com/questions/54258241/warning-iso-c-forbids-converting-a-string-constant-to-char-for-a-static-c

使用constexpr时遇到的小坑的更多相关文章

  1. OpenACC 计算规约时发现的小坑

    ▶ 使用 OpenACC 的 parallel 构件来计算规约,主要想说的是 win10 pgi 和 win10 WSL pgi 结果的不同和关于 for 循环的一个小坑 ● 正常的代码 #inclu ...

  2. 曲演杂坛--使用CTE时踩的小坑:No Join Predicate

    在一次系统优化中,意外发现一个比较“坑”的SQL,拿出来供大家分享. 生成演示数据: --====================================== --检查测试表是否存在 IF(O ...

  3. JDBC 连接 MySQL 时碰到的小坑

    最近从MS SQL Server换到了MySQL,已经是8.11版本了,安装的时候似乎还用了新的身份认证方式之类的,连接过程中也是磕磕绊绊,碰到很多奇奇怪怪的问题,在此记录下来. 驱动加载: 以前使用 ...

  4. activiti设置customSessionFactories时的一个小坑

    现象:activiti设置customSessionFactories不起作用,流程还是走原来的查询方法原因:新实现的XXXEntityManagerFactory的getSessionType方法返 ...

  5. 注意Android里TextView控件的一个小坑,用android:theme来设置样式时动态载入的layout会丢失该样式

    注意Android里TextView控件的一个小坑,用android:theme来设置样式时动态载入的layout会丢失该样式 这个坑,必须要注意呀, 比如在用ListView的时候,如果在List_ ...

  6. 关于CSS3中transform变换的小坑

    2017年6月30日15:05:46 今天在写一个demo的时候,发现CSS3中transform变换的一个特性. 首先,我先描述一下我发现的情况(问题再现): <div class=" ...

  7. Vue中应用CORS实现AJAX跨域,及它在 form data 和 request payload 的小坑处理

    基本概念部分(一):理解CORS 说道Vue的跨域AJAX,我想先梳理一遍CORS跨域,"跨域资源共享"(Cross-origin resource sharing),它是一个W3 ...

  8. mysql url 连接配置的一个小坑。 工作中不会遇到。 学习的时候会

    <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> & ...

  9. 关于sniff函数的一个小坑

    最近在用scapy模块写一个关于WiFi的脚本时用到sniff函数,其中遇到了一个小坑,记录如下: sniff函数是在指定网卡上每次嗅探到一个数据包后然后将它传给prn指定的函数.

随机推荐

  1. MyBatis(十一):MyBatis架构流程浅析

    架构分层 我们将MyBatis架构分为三层,分别为接口层.数据处理层和框架支撑层 接口层:提供外部接口调用的API,使用端通过这些API来操作数据库,接口层收到请求后会调用数据处理层完成具体的数据处理 ...

  2. 2019 GDUT Rating Contest III : Problem D. Lemonade Line

    题面: D. Lemonade Line Input file: standard input Output file: standard output Time limit: 1 second Memo ...

  3. Python读写配置文件模块--Configobj

    一.介绍 我们在项目的开发过程中应该会遇到这样的问题:我们的项目读取某个配置文件,然后才能按照配置的信息正常运行服务,当我们需要对修改服务的某些信息时,可以直接修改这个配置文件,重启服务即可,不用再去 ...

  4. webpack核心模块tapable用法解析

    前不久写了一篇webpack基本原理和AST用法的文章,本来想接着写webpack plugin的原理的,但是发现webpack plugin高度依赖tapable这个库,不清楚tapable而直接去 ...

  5. Bonuses on a Line Gym - 102569B

    题目链接:https://vjudge.net/problem/Gym-102569B 题意:数轴上有N个点,从0出发最多走t步问最多经过几个点. 思路:分开存负数点和整数点,然后枚举每个端点,某个点 ...

  6. 攻防世界 resver catch-me

    catch-me asis-ctf-quals-2016 附件给了个压缩包文件,重命名,解压,获取到elf文件 程序有两处关键比较 第一处: 这里进行动态调试,得到v3=0xB11924E1, byt ...

  7. Java入门环境的搭建

    入门环境搭建 Java帝国的诞生 C & C++ 1972年C诞生 贴近硬件,运行极快,效率极高 操作系统,编译器,数据库,网络系统等 指针和内存管理 1982年C++诞生 面向对象 兼容C ...

  8. > 与 < 差在哪?-- Shell十三问<第十一问>

    > 与 < 差在哪?-- Shell十三问<第十一问> 谈到 I/O redirection ,不妨先让我们认识一下 File Descriptor (FD) .程序的运算,在 ...

  9. Kotlin编写Processing程序(使用函数式编程思维和面向接口方式)

    写一例Kotlin编写的Processing程序,充分调用函数式编程思维和面向接口的编程思维,供自己和读者参考学习. 初衷 想要实现一行行的文字排版功能,每一行作为一个单位,可制定显示的位置.大小.文 ...

  10. 安装电脑思考到了Java设计模式:建造者模式

    目录 定义 意图 主要解决问题 何时使用 优缺点 结构 组装电脑的例子 定义 建造者模式是对象的创建型模式,可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表 ...