Is casting a C++ reference from unsigned char to double& safe?

Viewed 133

I am trying to implement a "table" of references for setting various code elements by an index lookup. Most of the underlying variables are double, but a few are different types like unsigned char.

Here is a simplified example:

static unsigned char foo = 0;     // Variable to set
static unsigned double bar = 0.0; // Variable to set
...
struct tableEntry {
    double& ref; // Reference to one of the above
    double min;
    double max;
};
struct tableEntry entries[] {
    { bar, 0.0, 1.0 },
    { (double&)foo, 0x0, 0xff },
};

When attempting to assign bar via the table, everything is fine.

entries[0].ref = 1.0;

This results in entries[0].ref == 1.0, and bar == 1.0, as expected. However, when assigning foo via the table...

entries[1].ref = 1.0;

This results in a value mismatch somehow, as if the reference is not able to be interpreted correctly, or isn't even referring to the right object. After this assignment, entries[1].ref == 1, but foo == 0.

Is this undefined behavior by the language, or am I misusing or abusing something? It's possible it is a discrepancy in versions of the standard, as I tested a similar example on repl.it with C++17 and it worked as I was expecting. This is not working as expected when compiling with our target compiler using mex in MATLAB, using Visual Studio 2017 as the compiler.

2 Answers

Reading an individual char as a double is undefined behavior as it breaks type aliasing rules, which is what you are doing. Converting from a char value to a double value using static_cast<double> is perfectly normal and defined though.

It is generally possible to reinterpret double as char, but only in specific scenarios:

Type aliasing

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

  • [...]
  • AliasedType is std::byte (since C++17), char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

(See reinterpret_cast conversion)

So we are allowed to examine a double (DynamicType) as a char[] (AliasedType), but not the other way around. Consider the fact that char is exactly one byte large and double will most likely be 8 bytes large. What is the value of the remaining 7 bytes when reinterpreting references? It is undefined.

Note that when casting references, a C-style cast like (double&) is equivalent to reinterpret_cast<double&>. I recommend using the explicit C++ casts like const_cast, reinterpret_cast and static_cast to avoid confusion.

Alas strict aliasing applies to references in a similar way to which it applies to pointers.

The behaviour of making use of the reference attained with (double&)foo is undefined.

Related