【发布时间】:2020-10-14 21:33:18
【问题描述】:
在 C++ 中,casting away constness 的概念由标准严格定义。诸如static_cast 和reinterpret_cast 之类的强制转换不允许在显式转换中丢弃常量。抛弃 constness 的定义在很大程度上依赖于限定转换定义,它表示两种类型可以执行到 similar 类型的限定转换。例如,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 的数组”,另一个是“未知边界的数组”
在第一种情况下,数组和指针并不相似。 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);
【问题讨论】:
-
代码会产生很多警告。我猜你在某个地方有 UB。
-
您确定您在标准行为实际定义的领域中坚定不移吗? ??????
-
@yeoman 我不是,但这就是我添加语言律师标签的原因:)
-
“抛弃常量”的定义并不关心相似性。注意 Ps 上的上标。
标签: c++ gcc casting clang language-lawyer