为什么隐式转换不会发生在类型转换的 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*
进行比较。由于 Base1
和 Base2
不能占用相同的内存,它们不能都位于内存的开头 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_cast
、it 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
的布局是完整已知的,虽然这个布局在技术上是实现定义的,但编译器准确地知道这个布局,因为 Base1
和 Base2
是无法访问的或虚拟的基地。
我们实际上可以通过提供无法访问的基础来看到失败的情况:
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*
进行比较需要将它们转换为通用类型,因此这就是将要发生的转换。如您所见,它保留了空值。
这样做的理由是,不可能从空指针值生成有效的外观值。如果你从一个空指针值开始,你会坚持下去。
我试图找到一种方法来静态断言派生的 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*
进行比较。由于 Base1
和 Base2
不能占用相同的内存,它们不能都位于内存的开头 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_cast
、it 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
的布局是完整已知的,虽然这个布局在技术上是实现定义的,但编译器准确地知道这个布局,因为 Base1
和 Base2
是无法访问的或虚拟的基地。
我们实际上可以通过提供无法访问的基础来看到失败的情况:
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
”, whereD
is a class type, can be converted to a prvalue of type “pointer to cvB
”, whereB
is a base class ofD
. IfB
is an inaccessible or ambiguous base class ofD
, 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*
进行比较需要将它们转换为通用类型,因此这就是将要发生的转换。如您所见,它保留了空值。
这样做的理由是,不可能从空指针值生成有效的外观值。如果你从一个空指针值开始,你会坚持下去。