考虑以下计划:
#include <algorithm>
#include <iostream>
#include <vector>
struct foo {
foo(int value)
: value_(value)
{
// perform range checks
}
int value() const {
return value_;
}
private:
int value_;
};
int main() {
std::vector<foo> values{0, 1, 2, 3, 4, 5};
std::for_each(std::begin(values), std::end(values),
[](foo& f){ std::cout << f.value(); });
std::cout << std::endl;
std::for_each(reinterpret_cast<const int*>(values.data()),
reinterpret_cast<const int*>(values.data()) + values.size(),
[](int i){ std::cout << i; });
}
使用Apple LLVM 6.0版(clang-600.0.54)(基于LLVM 3.5svn)编译后,它会产生以下输出(这正是我想要的):
012345
012345
第一次迭代是微不足道的。然而,第二次迭代不是通过迭代器执行,而是通过指向已经转换为const int*
的底层存储的指针执行。
我的问题是:该代码合法吗?
我的直觉是它。根据C ++ 11标准的§5.2.10/ 7(最终工作草案):
当“指向
更严格的条款T1
的指针”类型的prvalue v转换为该类型时 “指向 cvT2
的指针”,如果static_cast<cvT2*>(static_cast<cvvoid*>(v))
和T1
都是T2
,则结果为T2
标准布局类型(3.9)和T1
的对齐要求是 没有比{{1}}
如果我正确解释,那么上面的代码应该是正确的,对吧?如果没有,它可以成功吗?
答案 0 :(得分:5)
(在我的回答中,我使用了C ++ 14标准草案(N4140),这与C ++ 11在相关引用方面略有不同)
由于reinterpret_cast<const int*>(values.data())
:, [class.mem]/19
没问题
如果标准布局类对象具有任何非静态数据成员,则其地址与其第一个非静态数据成员的地址相同。 (...)[注意:因此,标准布局结构对象中可能存在未命名的填充,但不是在其开头,以实现适当的对齐。 - 后注]
关于解除引用,[expr.reinterpret.cast]/7
:
可以将对象指针显式转换为不同类型的对象指针。当对象指针类型的prvalue v转换为对象指针类型“指向cv
T
的指针”时,结果为static_cast<cv T*>(static_cast<cv void*>(v))
。
第一个static_cast
由[conv.ptr]/2
:
类型为“指向cv
T
的指针”的prvalue,其中T
是对象类型,可以转换为类型为“指针”的prvalue 到cvvoid
“。将指针的非空指针值转换为对象类型的结果为“指针指向 cvvoid
“表示内存中与原始指针值相同的字节地址。
第二个static_cast
- [expr.static.cast]/13
:
类型为“指向cv1 void的指针”的prvalue可以转换为类型为“指向cv2 T的指针”的prvalue(...)如果原始指针值表示内存中字节的地址A且A满足T的对齐要求,则结果指针值表示与原始指针值相同的地址,即A。
由于[class.mem]/19
,对齐要求得到满足,因此演员表工作正常。
但问题是除sizeof(foo) == sizeof(int)
的上述要求外,似乎无法保证std::complex
。可以将[class.mem]/19
中关于未命名填充的注释解释为只有在对齐时需要填充,因此在您的情况下不得有任何填充,但在我看来,这个注释太模糊了方面。
您可以做的是放入您的代码
static_assert(sizeof(foo) == sizeof(int), "");
// this may be paranoic but won't hurt
static_assert(alignof(foo) == alignof(int), "");
因此,如果违反要求,至少你的代码不会编译。
答案 1 :(得分:3)
这是对的。在某些条件下,指向结构的指针可以转换为指向它的第一个成员的指针。这是C的遗留保留,因为这是当时实现完全不继承的原因。
这在C ++11§9.2/ 20 [class.mem]中指定:
指向标准布局结构对象的指针,适当地使用转换 a
reinterpret_cast
,指向其初始成员(或者如果该成员是 比特字段,然后到它所在的单元,反之亦然。 [ 注意:因此可能有一个未命名的填充 标准布局结构对象,但必要时不在其开头 实现适当的对齐。 - 结束记录]