Alecto
Alecto

Reputation: 10740

How dangerous is reinterpret casting to objects that should have the same memory layout in C++?

I've written a series of function templates to convert arbitrary stuff to text as painlessly as possible. For example, print(std::pair<int, int> {13, 1}); will print {13, 1} and something longer like

std::vector<std::tuple<double, std::string>> vect;
for(int i=0;i<3;++i) {
    double root = sqrt(i);
    vect.push_back( {root, "sqrt " + std::to_string(i) } );
}
print(vect);

Will output: { {0, "sqrt 0" }, {1, "sqrt 1"}, {1.41421, "sqrt 2"} }

Let's say I have the following struct:

struct point { int x, y; };

How dangerous is it to write something like the following code?

std::vector<point> my_points;
//Add points into my_points;
print(reinterpret_cast<const std::vector<std::pair<int, int>>&>(my_points));

It compiles in gcc and it produces the expected output, although I'm concerned it could fail if someone were to try porting the code.

Upvotes: 0

Views: 125

Answers (3)

user877329
user877329

Reputation: 6200

Instead of passing a std::vector<T>, you could pass two const T*, one const T* and a length, or a struct of two pointers.

Now you know that there is no specialization (as with the case std::vector<bool>, see Why is vector<bool> not a STL container?) going on, and you also know that you have contiguous memory of T:s. Now provided that you know that

  1. Foos and Bars have exactly the same layout (alignment, size)
  2. Foos and Bars share used behavior (If there is a function f(Foo), and that you use that function in print, there must also be a semantically identical function f(Bar)). A more theoretical formulation would be something like: Foo is equivalent to Bar with respect to f.

Then, it should be safe, but you are on your own, because the compiler cannot really check (2). As an example, take two version of Point (lets use float instead of int):

struct PointXY{float x;float y;};

struct PointYX{float y;float x;};

float inner_product(PointXY p1,PointXY p2)
    {return p1.x*p2.x + p1.y*p2.y;}

float inner_product_with_important_y(PointXY p1,PointXY p2)
    {return 0.5f*( p1.x*p2.x + 4.0f*p1.y*p2.y );}

That is, you can safely call inner_product with any combination PointXY and PointYX (both (1) and (2) are fulfilled), but as soon as you call the anisotropic version, you will get wrong results ((1) is fulfilled, but not (2)).

Upvotes: 0

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

Very dangerous. You have undefined behavior. std::vector<std::pair<int, int>> and std::vector<Point> are completely, district and separated classes that have no relation to each other. Reinterpreting one to another will be undefined behavior.

In fact, there are cases defined by the standard that will happen to not work to. Try casting a std::vector<char> to std::vector<bool>. Even though char and bool have the same size, both vector are not compatible.

If you want to avoid copying the buffer, consider using templates:

template<typename T>
void print(const std::vector<T>& vec) {
    // ...
}

Even better, don't force vector. If you have a std::array<Point, n>, you might want your function to work:

template<typename T>
void print(const T& range) {
    // ...
}

Upvotes: 1

SergeyA
SergeyA

Reputation: 62583

First of all, you are not doing any dynamic casting here, since there is no a single call to dynamic_cast in your code. What you are doing here is the reinterpet_cast, produced by the c-cast.

Second of all, no, it is not safe and is undefined behavior on many levels.

Upvotes: 3

Related Questions