在这种情况下,clang 是错误的,gcc 是错误的,还是两者都是错误的 - 用成员指针抛弃 constness
Is clang wrong, gcc wrong, or both wrong in this case - casting away constness with member pointers
在C++中,casting away constness is rigorously defined by the standard. Casts such as static_cast
and reinterpret_cast
are not allowed to cast away constness in the explicit conversion. The definition of casting away constness heavily relies on the qualification conversion definition, which says that two types can perform a qualification conversion up to 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);
}
回忆相似的定义。 每个级别的类型必须属于类别:
... each Pi is “pointer to” ([dcl.ptr]), “pointer to member of class Ci of type” ([dcl.mptr]), “array of Ni”, or “array of unknown bound of” ([dcl.array])
要相似,每个Pi必须属于同一类别:
... corresponding Pi components are either the same or one is “array of Ni” and the other is “array of unknown bound of”
在第一种情况下,数组和指针不相似。 GCC 和 clang 正确地说 X[]
和 X*
类型不相似,因此 reinterpret_cast
开始忽略数组后的任何 const 限定符(因此 const int* const
可以转换为int*
)。一切都很好。
在第二种情况下,指针和成员指针不应该相似。 clang 正确地说类型不相似,因此 reinterpret_cast
开始。GCC 说类型相似,因此我们正在抛弃 constness。 我很确定 GCC 在这里是错误的。
在第三种情况下,我们正在比较两个不同classes的成员指针。 clang 和 GCC 都认为它们是相似的,但是如果我们仔细看草稿,它会说 "pointer to member of class Ci of type"。如果它们属于不同类型,我们不应该认为它们不相似吗? 这里clang和gcc都错了吗?
顺便说一下,MSVC 允许以上所有操作而没有警告(x64 msvc 19.27 on godbolt)。
编辑:第三种情况可以用更简单的方式重现:
const int T::* f;
reinterpret_cast<int F::*>(f);
I had a discussion with Richard Smith (the creator of clang) 并且我得出结论,如果我们传入 -pedantic
,所有三种情况都应该无法编译,而 MSVC 确实是有错误的那个。这是因为在每个示例中都存在一个 cv-decomposition,其中 reinterpret_cast<> 会丢弃 constness,因此理想情况下该操作不应该能够工作。然而,clang 有扩展允许它与警告一起工作。这不是 UB 本身。
在C++中,casting away constness is rigorously defined by the standard. Casts such as static_cast
and reinterpret_cast
are not allowed to cast away constness in the explicit conversion. The definition of casting away constness heavily relies on the qualification conversion definition, which says that two types can perform a qualification conversion up to 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);
}
回忆相似的定义。 每个级别的类型必须属于类别:
... each Pi is “pointer to” ([dcl.ptr]), “pointer to member of class Ci of type” ([dcl.mptr]), “array of Ni”, or “array of unknown bound of” ([dcl.array])
要相似,每个Pi必须属于同一类别:
... corresponding Pi components are either the same or one is “array of Ni” and the other is “array of unknown bound of”
在第一种情况下,数组和指针不相似。 GCC 和 clang 正确地说 X[]
和 X*
类型不相似,因此 reinterpret_cast
开始忽略数组后的任何 const 限定符(因此 const int* const
可以转换为int*
)。一切都很好。
在第二种情况下,指针和成员指针不应该相似。 clang 正确地说类型不相似,因此 reinterpret_cast
开始。GCC 说类型相似,因此我们正在抛弃 constness。 我很确定 GCC 在这里是错误的。
在第三种情况下,我们正在比较两个不同classes的成员指针。 clang 和 GCC 都认为它们是相似的,但是如果我们仔细看草稿,它会说 "pointer to member of class Ci of type"。如果它们属于不同类型,我们不应该认为它们不相似吗? 这里clang和gcc都错了吗?
顺便说一下,MSVC 允许以上所有操作而没有警告(x64 msvc 19.27 on godbolt)。
编辑:第三种情况可以用更简单的方式重现:
const int T::* f;
reinterpret_cast<int F::*>(f);
I had a discussion with Richard Smith (the creator of clang) 并且我得出结论,如果我们传入 -pedantic
,所有三种情况都应该无法编译,而 MSVC 确实是有错误的那个。这是因为在每个示例中都存在一个 cv-decomposition,其中 reinterpret_cast<> 会丢弃 constness,因此理想情况下该操作不应该能够工作。然而,clang 有扩展允许它与警告一起工作。这不是 UB 本身。