c++ - 在这种情况下是 clang 错误、gcc 错误还是两者都错误 - 用成员指针抛弃 constness
问题描述
在 C++ 中,标准严格定义了抛弃常量的概念。诸如static_cast
andreinterpret_cast
之类的强制转换不允许在显式转换中抛弃 constness。抛弃 constness 的定义很大程度上依赖于限定转换定义,它表示两种类型可以执行到相似类型的限定转换。reinterpret_cast
例如,然后可以对不相似的零件进行铸造。
这是Godbolt中也可用的示例:
struct T{};
struct F{};
void f() {
const int* const T::* const * const * const x {};
// pointer to array - works in clang (trunk),
// works in gcc (trunk)
reinterpret_cast<int* const T::* const (*) [2]>(x);
// member pointer to pointer - works in clang (trunk),
// fails in gcc (trunk)
reinterpret_cast<int* const * const * const * const>(x);
// member pointer to another member pointer type - fails in clang (trunk),
// fails in gcc (trunk)
reinterpret_cast<int* const F::* const * const * const>(x);
}
回忆一下相似的定义。每个级别的类型必须属于以下类别:
...每个 Pi 是“指向”([dcl.ptr])、“指向类型 Ci 的成员的指针”([dcl.mptr])、“Ni 数组”或“未知边界数组” ([dcl.array])
要相似,每个Pi 必须属于同一类别:
... 对应的 Pi 分量要么相同,要么一个是“Ni 数组”,另一个是“未知边界数组”</p>
在第一种情况下,数组和指针并不相似。GCC 和 clang 正确地表示类型X[]
和X*
不相似,因此reinterpret_cast
开始忽略数组之外的任何 const 限定符(因此const int* const
可以转换为int*
)。万事皆安。
在第二种情况下,指针和成员指针不应该相似。clang 正确地说类型不相似,因此reinterpret_cast
启动。GCC 说类型相似,因此我们抛弃了 constness。我很确定 GCC 在这里是错误的。
在第三种情况下,我们正在比较不同类的两个成员指针。clang 和 GCC 都认为它们是相似的,但是如果我们仔细查看草案,它会说“指向类型 Ci 的成员的指针”。如果它们是不同的类型,我们不应该认为它们不相似吗?clang 和 gcc 都错了吗?
顺便说一句,MSVC 允许上述所有操作而没有警告(godbolt 上的 x64 msvc 19.27)。
编辑:第三种情况可以以更简单的方式重现:
const int T::* f;
reinterpret_cast<int F::*>(f);
解决方案
我与 Richard Smith(clang 的创建者)进行了讨论,我得出的结论是,如果我们传入所有三个案例都应该无法编译,-pedantic
并且 MSVC 确实是有错误的案例。这是因为在每个示例中都存在一个 cv 分解,其中 reinterpret_cast<> 会抛弃常量,因此理想情况下该操作不应该能够工作。但是,clang 具有允许它处理警告的扩展。它本身不是UB。
推荐阅读
- laravel - 无法读取 Laravel Echo 中的事件
- gremlin - Gremlin - 使用 OR 步骤获取不同类型的连接顶点
- java - 我将复数提高到幂的方法有什么问题
- json - PL/SQL json_value 函数为大型 JSON 文档提供错误
- python - 多处理池 - 大多数工作人员已加载但仍处于空闲状态
- ssl - 负载平衡中 Google 管理的 SSL 证书的子域和定价
- flutter - Flutter、Fluro“匿名关闭”
- java - 如何使用 SQL Server Java 语言扩展进行日志记录?
- r - R中的new()和setClass()有什么区别?
- c# - 使用从 JS 代码到静态 Blazor 方法的注入服务