为什么隐式转换不会发生在类型转换的 nullptr 上

Why doesn't implicit conversion occur on a typecasted nullptr

我试图找到一种方法来静态断言派生的 class 指针可以安全地重新解释为指向基 class 的指针,当我遇到一些意想不到的事情时:

我原以为以下代码中的两个静态断言会失败。

class Base1 {
    int x;
};
class Base2 {
    int y;
};
class Derived : public Base1, public Base2 {
    int z;
};

// one of these should fail???
static_assert( (Derived *)nullptr == (Base1 *)nullptr, "err1" );
static_assert( (Derived *)nullptr == (Base2 *)nullptr, "err2" );
// and one of these as well???
static_assert( (Base1 *)(Derived *)nullptr == nullptr, "err3" );
static_assert( (Base2 *)(Derived *)nullptr == nullptr, "err3" );

对于第一对断言,因为它的参数是 Derived*Base1*/Base2* 类型,我本以为 Derived* 会被隐式转换到 Base1*Base2* 进行比较。由于 Base1Base2 不能占用相同的内存,它们不能都位于内存的开头 Derived 对象占用,所以其中一个转换应该增加指针值。那么不应该发现非零指针等于null。

同样,对于第二对,我希望显式转换为 Base1*Base2* 应该改变其中一个指针,但它们仍然等于 null。

这是怎么回事?

输入C-style cast:

让我们回顾一下下面的引述:

When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order: a) const_cast<new_type>(expression) b) static_cast<new_type>(expression)

如果您尝试使用 reinterpret_castit will fail compilation because reinterpret_cast is not a constant expression, but static_cast is does more than just cast for the type system. In this case static_cast likely tells the compiler to perform some offset based on the (known at compile-time) sizes of Base1, Base2, and Derived, but static_cast may involve implicit conversions, a call to the constructor of new_type or a call to a user-defined conversion operator.

此外,来自 static_cast 的文本还说明了以下内容:

2) If new_type is a pointer or reference to some class D and the type of expression is a pointer or reference to its non-virtual base B, static_cast performs a downcast. This downcast is ill-formed if B is ambiguous, inaccessible, or virtual base (or a base of a virtual base) of D.

9) A pointer to member of some class D can be upcast to a pointer to member of its unambiguous, accessible base class B. This static_cast makes no checks to ensure the member actually exists in the runtime type of the pointed-to object.

这实际上是因为 Derived 的布局是完整已知的,虽然这个布局在技术上是实现定义的,但编译器准确地知道这个布局,因为 Base1Base2 是无法访问的或虚拟的基地。

我们实际上可以通过提供无法访问的基础来看到失败的情况:

class Base{};

class Base1 : public Base
{
    int x;
};

class Base2 : public Base
{
    int y;
};

class Derived 
    : public Base1
    , public Base2 
{
    int z;
};

constexpr static Derived d{};

constexpr static const Derived* derived_ptr = &d;
constexpr static const Base1* base1_ptr = &d;
constexpr static const Base2* base2_ptr = &d;

// fail due to inaccessible base
static_assert(static_cast<const Derived*>(nullptr) == static_cast<const Base*>(nullptr), "err1" ); // fails
static_assert(static_cast<const Derived*>(derived_ptr) == static_cast<const Base*>(nullptr), "err2" ); // fails

// succeed
static_assert(static_cast<const Derived*>(derived_ptr) == static_cast<const Base1*>(base1_ptr), "err3" );
static_assert(static_cast<const Derived*>(derived_ptr) == static_cast<const Base2*>(base2_ptr), "err4" );
// and one of these as well???
static_assert(static_cast<const Base1*>(static_cast<const Derived*>(derived_ptr)) == base1_ptr, "err5" );
static_assert(static_cast<const Base2*>(static_cast<const Derived*>(derived_ptr)) == base2_ptr, "err6" );

空指针始终是空指针,并且一直是空指针。

[conv.ptr] (emphasis mine)

3 A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class of D. If B is an inaccessible or ambiguous base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

由于将 Derived*Base* 进行比较需要将它们转换为通用类型,因此这就是将要发生的转换。如您所见,它保留了空值。

这样做的理由是,不可能从空指针值生成有效的外观值。如果你从一个空指针值开始,你会坚持下去。