数组指针何时可转换为不同类型的数组指针?
When is a pointer-to-array convertible to a pointer-to-array of different type?
我正在检查 cppreference documentation 的 std::unique_ptr
,并注意到 C++17 似乎进行了一些有趣的更改。特别是,std::unique_ptr<T[]>
的特化现在接受模板参数,而以前只接受 std::unique_ptr::pointer
参数。例如,这里是 std::unique_ptr<T[]>
的 reset
成员函数之一的声明:
template <typename U> void reset(U p);
网站声明:
Behaves the same as the reset
member of the primary template, except that it will only participate in overload resolution if either
U
is the same type as pointer
, or
pointer
is the same type as element_type*
and U
is a pointer type V*
such that V(*)[]
is convertible to element_type(*)[]
.
我假设这样做是为了安全起见 - 你不想对指向派生类型数组的指针执行 delete[]
,该数组被分配给指向其基类型的指针(在C++17 这被标记为已删除)。正如预期的那样,这段代码编译得很好:
#include <type_traits>
struct foo {};
struct bar : public foo {};
static_assert(!std::is_convertible_v<bar(*)[], foo(*)[]>);
然而,有趣的是,下面的 不 编译,两个 static_assert
都失败了:
#include <type_traits>
struct foo {};
struct bar : public foo {};
static_assert(std::is_convertible_v<bar*(*)[], foo*(*)[]>);
static_assert(std::is_convertible_v<std::unique_ptr<bar>(*)[], std::unique_ptr<foo>(*)[]>);
这是为什么?这个重载会在什么场景下使用?
struct foo {};
struct bar : public foo {};
在这种微不足道的情况下,foo*
的值表示很可能与 bar*
的值表示完全相同,因此 可以 可以在它们的数组之间进行转换。但是,一旦 bar
变得更加复杂,它就不再成立。考虑一下:
struct foo {};
struct qux {};
struct bar : qux, foo {};
现在,bar*
可隐式转换为 foo*
,但该转换不保留其精确值:它需要从 qux
到 foo
的偏移量.
同样,一旦出现虚拟对象、多访问控制级别等,基 class 子对象的地址就会与最派生对象的地址不同。这样的指针甚至可以有不同的大小(1).
这就是为什么只有少数情况下 derived*
可以用作 base*
而无需进行值更改转换。我的猜测是,考虑到有限的情况会使标准复杂化,但没有什么好处,因此根本不允许在此类指针数组之间进行转换。
(1) 这是一个有点晦涩的案例,但如果 C++ 编译器受到设计不当的 C ABI 的限制,它可能会发生。 This answer 讨论了这种情况。
这基本上是 "you can pass a less const-y pointer if it's safe to do so" 的一种表达方式,例如 int* p = /*...*/; unique_ptr<const int []> up; up.reset(p);
对于不同的类型 U
和 V
,U(*)[]
可(隐式)转换为 V(*)[]
的唯一情况是通过 qualification conversion,即,当您在类型的正确位置添加 const
/volatile
时。确切的规则很复杂(因为它们处理任意嵌套 pointers/pointer-to-members/arrays;如果您想知道,请单击 link),但它们基本上只在安全时才允许转换; unique_ptr
的规范随后利用了这一事实,因此它不必重新定义 "safe",但代价是使意图变得更加神秘。
我正在检查 cppreference documentation 的 std::unique_ptr
,并注意到 C++17 似乎进行了一些有趣的更改。特别是,std::unique_ptr<T[]>
的特化现在接受模板参数,而以前只接受 std::unique_ptr::pointer
参数。例如,这里是 std::unique_ptr<T[]>
的 reset
成员函数之一的声明:
template <typename U> void reset(U p);
网站声明:
Behaves the same as the
reset
member of the primary template, except that it will only participate in overload resolution if eitherU
is the same type aspointer
, orpointer
is the same type aselement_type*
andU
is a pointer typeV*
such thatV(*)[]
is convertible toelement_type(*)[]
.
我假设这样做是为了安全起见 - 你不想对指向派生类型数组的指针执行 delete[]
,该数组被分配给指向其基类型的指针(在C++17 这被标记为已删除)。正如预期的那样,这段代码编译得很好:
#include <type_traits>
struct foo {};
struct bar : public foo {};
static_assert(!std::is_convertible_v<bar(*)[], foo(*)[]>);
然而,有趣的是,下面的 不 编译,两个 static_assert
都失败了:
#include <type_traits>
struct foo {};
struct bar : public foo {};
static_assert(std::is_convertible_v<bar*(*)[], foo*(*)[]>);
static_assert(std::is_convertible_v<std::unique_ptr<bar>(*)[], std::unique_ptr<foo>(*)[]>);
这是为什么?这个重载会在什么场景下使用?
struct foo {};
struct bar : public foo {};
在这种微不足道的情况下,foo*
的值表示很可能与 bar*
的值表示完全相同,因此 可以 可以在它们的数组之间进行转换。但是,一旦 bar
变得更加复杂,它就不再成立。考虑一下:
struct foo {};
struct qux {};
struct bar : qux, foo {};
现在,bar*
可隐式转换为 foo*
,但该转换不保留其精确值:它需要从 qux
到 foo
的偏移量.
同样,一旦出现虚拟对象、多访问控制级别等,基 class 子对象的地址就会与最派生对象的地址不同。这样的指针甚至可以有不同的大小(1).
这就是为什么只有少数情况下 derived*
可以用作 base*
而无需进行值更改转换。我的猜测是,考虑到有限的情况会使标准复杂化,但没有什么好处,因此根本不允许在此类指针数组之间进行转换。
(1) 这是一个有点晦涩的案例,但如果 C++ 编译器受到设计不当的 C ABI 的限制,它可能会发生。 This answer 讨论了这种情况。
这基本上是 "you can pass a less const-y pointer if it's safe to do so" 的一种表达方式,例如 int* p = /*...*/; unique_ptr<const int []> up; up.reset(p);
对于不同的类型 U
和 V
,U(*)[]
可(隐式)转换为 V(*)[]
的唯一情况是通过 qualification conversion,即,当您在类型的正确位置添加 const
/volatile
时。确切的规则很复杂(因为它们处理任意嵌套 pointers/pointer-to-members/arrays;如果您想知道,请单击 link),但它们基本上只在安全时才允许转换; unique_ptr
的规范随后利用了这一事实,因此它不必重新定义 "safe",但代价是使意图变得更加神秘。