3

Is reinterpret_cast safe for this, and is it the best way to do this?

For example, in the code below, I have a class called ibytestream, which allows the reading of uint16_ts and int16_ts from it. ibytestream::next is a vector<unsigned char>::iterator.

inline ibytestream& operator>>(ibytestream& stream, uint16_t& data) {
    data = 0;
    data |= *stream.next++;
    data <<= 8;
    data |= *stream.next++;
    return stream;
}

inline ibytestream& operator>>(ibytestream& stream, int16_t& data) {
    return stream >> reinterpret_cast<uint16_t&>(data);
}

I don't want to duplicate the code for converting the bytes to an integer, so I used reinterpret_cast for the signed version to reuse the code from the unsigned version. It works fine on my machine, but will it work in general on other modern machines?

Bernard
  • 5,209
  • 1
  • 34
  • 64
  • Wouldn't this violate the strict aliasing rule? http://stackoverflow.com/q/98650/417197 – André Dec 23 '16 at 08:37
  • 2
    @Andre the rule allows for aliasing between an integer type and its signed/unsigned variant – M.M Dec 23 '16 at 08:54

2 Answers2

3

Yes, this is safe.

Three parts of the standard apply to make this determination:

  1. Alignment requirements of signed and unsigned types are the same
  2. Pointer casts between pointers to types with identical alignment requirements are allowed
  3. When casts between glvalues are performed, the cast is valid if the cast between the corresponding pointers is valid.

For each of the standard signed integer types, there exists a corresponding (but different) standard unsigned integer type: unsigned char, unsigned short int, unsigned int, unsigned long int, and unsigned long long int, each of which occupies the same amount of storage and has the same alignment requirements.

An object pointer can be explicitly converted to an object pointer of a different type. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value.

A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    I think you're missing the part which M.M refers to in his comment; that you can access a signed integer object through an expression of corresponding unsigned type. – MSalters Dec 23 '16 at 09:44
1

Yes, that should be perfectly fine. (Moving between ints and byte arrays have potential endian-ness issues, but that's another matter that applies to both signed and unsigned numbers.)

Something completely different: This bit:

data = 0;
data |= *stream.next++;

...can be simplified:

data = *stream.next++;
Petter Hesselberg
  • 5,062
  • 2
  • 24
  • 42
  • I don't think my code will have endianness issues. The bytes will always be read in big-endian regardless of the endianness of the underlying machine. Also, I didn't do the simplification so that the code would look consistent (i.e. `data |= *stream.next++;` repeated over multiple lines), and I would assume my compiler is smart enough to optimize it. – Bernard Dec 23 '16 at 09:30
  • It's only an issue if you need portability across architectures, and it would seem you don't need it. All good. – Petter Hesselberg Dec 23 '16 at 09:39