首页 > 解决方案 > 在这种情况下是 clang 错误、gcc 错误还是两者都错误 - 用成员指针抛弃 constness

问题描述

在 C++ 中,标准严格定义了抛弃常量的概念。诸如static_castandreinterpret_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);

标签: c++gcccastingclanglanguage-lawyer

解决方案


我与 Richard Smith(clang 的创建者)进行了讨论,我得出的结论是,如果我们传入所有三个案例都应该无法编译,-pedantic并且 MSVC 确实是有错误的案例。这是因为在每个示例中都存在一个 cv 分解,其中 reinterpret_cast<> 会抛弃常量,因此理想情况下该操作不应该能够工作。但是,clang 具有允许它处理警告的扩展。它本身不是UB。


推荐阅读