views:

659

answers:

4

I'm wondering if it is necessary to reinterpret_cast in the function below. ITER_T might be a char*, unsigned char*, std::vector<unsigned char> iterator, or something else like that. It doesn't seem to hurt so far, but does the casting ever affect how the bytes are copied at all?

template<class ITER_T>
char *copy_binary(
  unsigned char length,
  const ITER_T& begin)
{
  // alloc_storage() returns a char*
  unsigned char* stg = reinterpret_cast<unsigned char*>(alloc_storage(length));
  std::copy(begin, begin + length, stg);
  return reinterpret_cast<char*>(stg);
}
+5  A: 

reinterpret_casts are used for low-level implementation defined casts. According to the standard, reinterpret_casts can be used for the following conversions (C++03 5.2.10):

  • Pointer to an integral type
  • Integral type to Pointer
  • A pointer to a function can be converted to a pointer to a function of a different type
  • A pointer to an object can be converted to a pointer to an object of different type
  • Pointer to member functions or pointer to data members can be converted to functions or objects of a different type. The result of such a pointer conversion is unspecified, except the pointer a converted back to its original type.
  • An expression of type A can be converted to a reference to type B if a pointer to type A can be explicitly converted to type B using a reinterpret_cast.

That said, using the reinterpret_cast is not a good solution in your case, since casting to different types are unspecified by the standard, though casting from char * to unsigned char * and back should work on most machines.

In your case I would think about using a static_cast or not casting at all by defining stg as type char *:

template<class ITER_T>
char *copy_binary(
  unsigned char length,
  const ITER_T& begin)
{
  // alloc_storage() returns a char*
  char* stg = alloc_storage(length);
  std::copy(begin, begin + length, stg);
  return stg;
}
Wolfgang Plaschg
+1 Looks like a good answer to me -- it tells you when to use reinterpret_cast and that you should not use it here.
Clinton Blackmore
This is all fine except the part about using 'static_cast'. My understanding is that static cast can only be used to convert between related pointer types and `void*`. 'unsigned char' and 'char' are unrelated types according to the standard and so this case is only covered by the 4th bullet in reinterpret_cast.
Richard Corden
+1  A: 

The code as written is working as intended according to standard 4.7 (2), although this is guaranteed only for machines with two's complement representation.

If alloc_storage returns a char*, and 'char' is signed, then if I understand 4.7 (3) correctly the result would be implementation defined if the iterator's value type is unsigned and you'd drop the cast and pass the char* to copy.

Rüdiger Hanke
A: 

So if I get it right, the cast to unsigned char is to gaurantee an unsigned byte-by-byte copy. But then you cast it back for the return. The function looks a bit dodgy what exactly is the context/reason for setting it up this way? A quick fix might be to replace all this with a memcpy() (but as commented, do not use that on iterator objects) -- otherwise just remove the redundant casts.

nj
You should be careful using memcpy with iterators. In this case he is using a vector, but contained data not always is contiguous.
fnieto
Oh yup, you're absolutely right.
nj
Here's the context: I'm working with a StringSlice class that keeps a char* and the substring range. However, some of the data I keep in the StringSlice should really be interpreted as unsigned. The class that has the StringSlice member allows someone to set the data with either unsigned char's or signed char's several ways, and there are methods to retrieve the data as a StringSlice, an unsigned char*, or copy the data as unsigned char's
And this function you've posted above links in at.. At the latter, to retrieve a copy of the data in a StringSlice?
nj
A: 

The short answer is yes, it could affect.

char and unsigned char are convertible types (3.9.1 in C++ Standard 0x n2800) so you can assign one to the other. You don't need the cast at all.

[3.9.1] ... A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements; that is, they have the same object representation.


[4.7] ...

2 If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type).

[ Note: In a two’s complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). —end note ]

3 If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.

Therefore even in the worst case you will get the best (less implementation-defined) conversion. Anyway in most implementations this will not change anything in the bit pattern, and you will not even have a conversion if look into the generated assembler.

template<class ITER_T>
char *copy_binary( unsigned char length, const ITER_T& begin)
{
  char* stg = alloc_storage(length);
  std::copy(begin, begin + length, stg);
  return stg;
}

Using reinterpret_cast you depend on the compiler:

[5.2.10.3] The mapping performed by reinterpret_cast is implementation-defined. [ Note: it might, or might not, produce a representation different from the original value. —end note ]

Note: This is an interesting related post.

fnieto