Cast a pointer to struct to a pointer to the only member of that struct

1k Views Asked by At

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 is static_cast<cvT2*>(static_cast<cvvoid*>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1

If I interpret this correctly, then the code above should be correct, right? If not, can it be made to work?

2

There are 2 best solutions below

0
On BEST ANSWER

(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 is static_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,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a non-null pointer value of a pointer to object type to a “pointer to cv void” 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.

5
On

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 ]