Is it safe to `reinterpret_cast` two unrelated vector of objects when those objects have the same binary representation?

Viewed 123

I have newtypes around int, as well as classes that contains those newtype.

class IndexA {
    int i;
};
class A {
    IndexA index;
};

// this mimic exactly the hierarchy of `A`
struct IndexB {
    int i;
};
struct B {
    IndexB index;
};

Given that A and B share the exact same binary layout, does the following code contains undefined behavior or is it safe?

std::vector<A> vfoo {...};
std::vector<B> vbar {
    std::move(
        reinterpret_cast<std::vector<B>&>(vfoo)
    )
};

I would have liked to be able to do a zero copy move transformation from std::vector<A> to std::vector<B>. Is it possible?

In my code:

  • A and B are really just a wrapper around int, so they have exactly the same binary representation.
  • The struct A only contains a mix of IndexA, std::optional<IndexA>, std::variant<IndexA, other types like IndexA>, or std::vector<IndexA or std::optional<IndexA>, …>. I think that I can assume that they have the same representation.
  • B is a strict copy of A, where every instances of IndexA are replaced by IndexB. The fields order is kept.
2 Answers

Is it safe to reinterpret_cast two unrelated vector of objects when those objects have the same binary representation?

No, it is not safe. The behaviour of the example program is undefined.

I would have liked to be able to do a zero copy move transformation from std::vector<A> to std::vector<B>. Is it possible?

No, it is not possible.


Something that you can do and is somewhat close to what you're trying is to have a vector of union of those types:

union IndexAB {
    IndexA A;
    IndexB B;
};

std::vector<IndexAB> vfoo {...};
for (IndexAB& u : vfoo) {
    std::cout << u.A.i;
    u.B.i = 42;
}

Whether A or B union member were activated doesn't matter. We can access either member regardless.

Note that this doesn't apply to all unions in general, but only in this particular case of standard layout structs, and specifically the members within their common initial sequence.

The standard does not allow you to convert between pointers of unrelated types, regardless of their binary layout:

struct A{ int i; };
struct B{ int i; };

B b{};
A* pa = reinterpret_cast<A*>(&b);
pa->i = 42; // undefined behavior

Related