Consider the following program:
#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; });
}
After compiling it with Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn), it produces the following output (which is exactly what I want):
012345
012345
The first iteration is trivial. The second iteration however is performed not through iterators, but through pointers to the underlying storage which have been cast to const int*.
My question is: Is that code legal?
My intuition is that it is. According to §5.2.10/7 of the C++11 standard (final working draft):
When a prvalue v of type “pointer to
T1” is converted to the type “pointer to cvT2”, the result isstatic_cast<cvT2*>(static_cast<cvvoid*>(v))if bothT1andT2are standard-layout types (3.9) and the alignment requirements ofT2are no stricter than those ofT1
If I interpret this correctly, then the code above should be correct, right? If not, can it be made to work?
(In my answer I use C++14 standard draft (N4140), which is a bit different from C++11 with regard to relevant quotes)
reinterpret_cast<const int*>(values.data()) is fine because of [class.mem]/19:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. (...) [ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. —end note ]
And regarding dereferencing, [expr.reinterpret.cast]/7:
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv
T”, the result isstatic_cast<cv T*>(static_cast<cv void*>(v)).
First static_cast is covered by [conv.ptr]/2:
A prvalue of type “pointer to cv
T,” whereTis an object type, can be converted to a prvalue of type “pointer to cvvoid”. The result of converting a non-null pointer value of a pointer to object type to a “pointer to cvvoid” represents the address of the same byte in memory as the original pointer value.
Second static_cast - [expr.static.cast]/13:
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T,” (...) If the original pointer value represents the address A of a byte in memory and A satisfies the alignment requirement of T, then the resulting pointer value represents the same address as the original pointer value, that is, A.
Alignment requirement is satisfied because of [class.mem]/19, so the cast works fine.
But the problem is that there seem to be no guarantee that sizeof(foo) == sizeof(int) except aforementioned requirement for std::complex. One can interpret the note about unnamed padding from [class.mem]/19 as allowing padding only if it's needed for alignment, so there must not be any padding in your case, but in my opinion that note is too vague in this regard.
What you can do is put in your code
static_assert(sizeof(foo) == sizeof(int), "");
// this may be paranoic but won't hurt
static_assert(alignof(foo) == alignof(int), "");
So at least your code won't compile if the requirements are violated.
It is correct. A pointer to a struct may be cast to a pointer to it's first member, under certain conditions which are met here. It's a legacy holdover from C, as this was how totally-not-inheritance was implemented back then.
This is specified in C++11 §9.2/20 [class.mem]:
A pointer to a standard-layout struct object, suitably converted using a
reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. [ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. — end note ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With