在理论或实践中,将
std::pair<T1, T2> const &
重新解释为std::pair<T1 const, T2> const &
是否安全,假设程序员没有故意进行像特化std::pair<T1 const, T2>
这样奇怪的操作?std::pair<T1, T2> const &
重新解释为std::pair<T1 const, T2> const &
是否安全,假设程序员没有故意进行像特化std::pair<T1 const, T2>
这样奇怪的操作?这样做是不可移植的。
std::pair
的要求在第20.3条中列出。第17.5.2.3条澄清了:
第18到30条和附录D不指定类的表示,并且有意省略了类成员的规范。实现可以根据需要定义静态或非静态类成员,或两者都定义,以实现第18到30条和附录D中指定的成员函数的语义。
这意味着,一个实现可以合法地(尽管极其不可能)包含一个部分特化,例如:
template<typename T1, typename T2>
struct pair<T1, T2>
{
T1 first;
T2 second;
};
template<typename T1, typename T2>
struct pair<const T1, T2>
{
T2 second;
const T1 first;
};
需要注意的是,这些类型显然不兼容布局。根据规则,还可以在first
和/或second
之前包含其他非静态数据成员的变体。
现在,考虑已知布局的情况会更有趣一些。尽管Potatoswatter指出了DR1334,声称T
和const T
不兼容布局,但标准提供了足够的保证,使我们能够在大部分情况下实现:
template<typename T1, typename T2>
struct mypair<T1, T2>
{
T1 first;
T2 second;
};
mypair<int, double> pair1;
mypair<int, double>* p1 = &pair1;
int* p2 = reinterpret_cast<int*>(p1); // legal by 9.2p20
const int* p3 = p2;
mypair<const int, double>* p4 = reinterpret_cast<mypair<const int, double>*>(p3); // again 9.2p20
std::pair
,我们无法应用9.2p20规则,因为我们不知道first
实际上是初始成员,这一点没有明确说明。first
和 second
不是私有成员(根据第17.5.2.3节的标题),它们也没有标记为 // exposition only
。这表明对我来说 pair
是一个特殊情况。 - ecatmurstd::pair
也会更改布局,并且在不同的特化中可能会以不同的方式进行。我认为最好相信规则说的是什么:第20条款中的类定义不限制布局,它们只是存在所需成员的列表。 - Ben Voigtcomplex<T>
的布局,与本答案中§17.5.2.3是绝对事实的假设相矛盾。因此,可以证明这种绝对的实现自由假设不成立。考虑到这一点,§20.3.2直接指定了std::pair<T, U>
的布局(在第一个成员后填充),未标记为阐述。 - Cheers and hth. - Alfstd::complex<T>
视为T[2]
数组的处理语义必须明确说明的事实,强烈支持解释17.5.2.3适用于18到30条款中列出的每个类定义。 - Ben Voigtpair
在标准的第20.3.2节中定义为具有以下数据成员:
template <class T1, class T2>
struct pair {
T1 first;
T2 second;
};
这意味着对于具体类型 T1
、T2
、pair<T1, T2>
和 pair<const T1, T2>
,它们分别保证拥有以下数据成员:
struct pair<T1, T2> {
T1 first;
T2 second;
};
struct pair<const T1, T2> {
const T1 first;
T2 second;
};
T1
和T2
都是标准布局,那么pair<T1,T2>
和pair<const T1,T2>
都是标准布局。如上所述,根据DR1334,它们不是布局兼容 (3.9p11),但根据9.2p19,它们可以reinterpret_cast
到它们各自的T1
或const T1
第一个成员。根据9.2p13,T2
第二个成员必须位于第一个成员之后(即具有更高的地址),并且根据1.8p5必须立即位于第一个成员之后,使对象在考虑对齐后连续(9.2p19)。
我们可以使用offsetof
检查这一点(它适用于标准布局类型):
static_assert(offsetof(pair<T1, T2>, second) ==
offsetof(pair<const T1, T2>, second), "!");
pair<T1, T2>
和pair<const T1, T2>
具有相同的布局后,按3.9.2p3所述,在向前转换时进行转换并使用结果访问成员是有效的:
因此,仅当如果类型
T
的对象位于地址A
处,则值为A
的类型cvT*
指针被认为是指向该对象的指针,无论该值如何获得。
std::is_standard_layout<std::pair<T1,T2>>::value
为true
时,reinterpret_cast
才是安全的。T1
和const T1
实际上不是布局兼容的。我不确定我是否同意这个观点,但你需要一些证据来支持你的说法,即它们是布局兼容的。 - Ben Voigtstd::pair<T1, T2>
和std::pair<const T1, T2>
具有相等的大小、相等的填充、相等的offsetof
等。这两种类型可以具有不同数量的填充,仍然每个类型都“占用连续的存储字节”。 - ascheplerconst
和非const
对象的相同表示。const T&
可以绑定到任意一个对象。但是当一个或两个模板类型参数标记为const
时,这并不排除std::pair
具有不同布局的特化情况。也许有其他编程语言可以实现这一点吗? - Ben Voigt
std::map
;) - Potatoswatterstd::map
不允许你规定value_type
是什么。它必须是std::pair< key const, mapped >
。如果使用不当,它会干扰你的程序设计并违反关注点分离原则。(因此,我怀疑最实际的解答应该解释如何适应std::set
或其他容器。) 但这个问题足够真实,并且在已经“锁定”为std::map
的设计中很常见,因此这不是一个 XY 问题。 - Potatoswatterstd::pair<T1 const, T2> q(p.first, p.second)
,看看编译器是否会消除复制。或者使用智能指针。 - Maxim Egorushkin