Discussion:
pointer_cast? (C++ style casting between unrelated pointer types)
(too old to reply)
Brian Neal
2006-02-15 13:33:39 UTC
Permalink
In Item 92 of Sutter and Alexandrescu's C++ Coding Standards book, they
argue you should avoid using reinterpret_cast.

In the case of casting between unrelated pointer types, they recommend
that instead of doing this:

T1* p1 = ...;
T2* p2 = reinterpret_cast<T2*>(p1);

that you do this:

T1* p1 = ...;
void* pv = p1;
T2* p2 = static_cast<T2*>(pv);

I have started to follow this advice, as in the lowest layers of our
code base we have to do this a lot (for example, streaming bytes in/out
of API's beyond our control, message stack processing, etc...all low
level stuff that is carefully hidden from the rest of our software).

I was wondering if it would be useful to introduce a set of functions:

template <class T>
T pointer_cast(void* p)
{
return static_cast<T>(p);
}

template <class T>
T pointer_cast(const void* p)
{
return static_cast<T>(p);
}

template <class T>
T pointer_cast(volatile void* p)
{
return static_cast<T>(p);
}

template <class T>
T pointer_cast(const volatile void* p)
{
return static_cast<T>(p);
}

Then you can say things like:

T1* p1 = ...;
T2* p2 = pointer_cast<T1*>(p1);

Its a bit more notationally cleaner than introducing a stray void*, or
by doing two static_casts on one line:

These functions are a bit safer than doing old-school C-style casts
like T2* p2 = (T2*) p1; since they will still flag changes in const and
volatile as errors.

Could I get some feedback on this please? Thanks.


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Thomas Maeder
2006-02-15 17:26:00 UTC
Permalink
Post by Brian Neal
T1* p1 = ...;
T2* p2 = pointer_cast<T1*>(p1);
Its a bit more notationally cleaner than introducing a stray void*,
These functions are a bit safer than doing old-school C-style casts
like T2* p2 = (T2*) p1; since they will still flag changes in const
and volatile as errors.
And because pointer_cast is easily "greppable" while.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Falk Tannhäuser
2006-02-16 02:00:36 UTC
Permalink
Post by Brian Neal
In Item 92 of Sutter and Alexandrescu's C++ Coding Standards book, they
argue you should avoid using reinterpret_cast.
In the case of casting between unrelated pointer types, they recommend
T1* p1 = ...;
T2* p2 = reinterpret_cast<T2*>(p1);
T1* p1 = ...;
void* pv = p1;
T2* p2 = static_cast<T2*>(pv);
AFAIK they also recommend to avoid static_cast on pointers...
What does the second variant buy you over the reinterpret_cast?
What does it give you for guarantees in considering the Standard
that reinterpret_cast doesn't?
When converting a T1* to a T2* in one of the aforementioned manners,
there *are* some potential problems:
1° The T1 object the source pointer pointed to may not have the
proper alignment for T2 when dereferencing the result of the
conversion,
2° The object representation of said T1 object may consist of a bit
pattern that is invalid for the type T2 (presumably triggering some
trap when trying to access said object as if it were of type T2),
3° The value of a T1 object reinterpreted as being of type T2 is
implementation defined (unless the Standard says otherwise, as for
example in § 3.9.1/3 or § 9.2/17).
(Note that 1° and 2° are guaranteed not to occur when T2 is 'unsigned char'.)
However, none of these potential problems will go away when you replace
the reinterpret_cast by the conversion to 'void cv*' followed by static_cast.

I think that "you should avoid using..." doesn't mean "never, never use..."
but rather "think twice before you use..." - when using such low-level
features depending on the object representation (§ 3.9/4) of your
objects, you simply should know what you are doing.

Falk

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Brian Neal
2006-02-18 04:22:03 UTC
Permalink
Post by Falk Tannhäuser
AFAIK they also recommend to avoid static_cast on pointers...
Yes, with respect to dynamic_cast, not to this situation.
Post by Falk Tannhäuser
What does the second variant buy you over the reinterpret_cast?
What does it give you for guarantees in considering the Standard
that reinterpret_cast doesn't?
When converting a T1* to a T2* in one of the aforementioned manners,
[snip]

Yes, yes I know all of those things. I'm not trying to do anything
tricky. I built up the data, but I must send it through an interface as
another pointer type, and when it pops out on the other side, I know I
can cast it back safely.

So you are telling me you never had data as a char*, but some API you
had to use and could not change took an unsigned char*? Have you ever
done much low level programming? I'm working with network stacks and
device drivers.
Post by Falk Tannhäuser
I think that "you should avoid using..." doesn't mean "never, never use..."
but rather "think twice before you use..." - when using such low-level
features depending on the object representation (§ 3.9/4) of your
objects, you simply should know what you are doing.
I do know what I am doing here, and I am taking the authors advice
about not using reinterpret_cast. Using reinterpret_cast here doesn't
feel right, as I am doing nothing that is non-portable. Or at least
nothing that a C programmer would not bat an eye at. :-)

So, if indeed what I'm doing is portable, reinterpret_cast sends the
wrong message. I am simply looking for cleaner and safer alternatives
than C-style casts or littering the code with extra void pointers.


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Carl Barron
2006-02-19 15:08:50 UTC
Permalink
Post by Brian Neal
So, if indeed what I'm doing is portable, reinterpret_cast sends the
wrong message. I am simply looking for cleaner and safer alternatives
than C-style casts or littering the code with extra void pointers.
I am sure the book is geared to more general programming than writing
device drivers:) That said I think it is not good programming proactice
to in general place C-style or reinterpret_casts, or convert to void *
and static cast. I similiar avoid nakeed new and delete as well but my
code often contains these 'under the hood'.

so a safer approach over naked casting and getting the same results with
some type safety, as well as this is not a fudge but this is intentional
message might be to use an inlined template function to do the casting
using metafunctions [classes/structs with a typename .... type; in them]
to cause compiler errors for stupid things like passing a double off as
a char *:)

// in general this is purposely incomplete, so bad usuage generates a
compiler error.

template <class T> struct driver_cast_impl;

template <> struct driver_cast_impl<char>
{typedef unsigned char *type;};

template <> struct driver_cast_impl<unsigned char>
{ typedef unsigned char *type;};

// other needed types similiar.

template <class T>
inline
typename driver_cast_impl<T>::type driver_cast(T *x)
{
return reinterpret_cast<driver_cast_impl<T>::type>(x);
}

a decent compiler should produce the same production code with
driver_cast as opposed to raw reinterpret_casts,or raw C-style casts.

placing these driver_cast templates in a header means the coder writes
driver_cast(x) instead of reinterpret_cast<unsigned char *>(x) in the
code and is more readable and more self documenting as well at probably
zero cost in production code.

that is
char *pc = ...
// ...
unsigned char *puc =
#ifdef USE_REINTERPRET
reinterpret_cast<unsigned char *>(pc);
#else
unsigned char *puc_2 = driver_cast(x);
#endif

probably produce identical production code whether USE_REINTEPRET is
defined or not.

I'd consider this as a model of 'how to cast in a C++ manner'.
Low level programming often requires 'dirty work' but like a car the
'engine parts' belong 'under the hood'.:)

boost::enable_if and boost:::mpl can make more general solutions in
about the same size header...

template <class T>
boost::enable_if
<
boost::mpl::contains
<
boost::mpl::vector<char,signed char,unsigned char>,
T // order of args might be revesrsed , from human memory:)
Post by Brian Neal
,
unsigned char
Post by Brian Neal
::type *driver_cast(T *x)
{
return reinterpret_cast<unsigned char *>(x);
}

looks like a boost,boost::mpl solution only allowing char types to be
cast to unsigned char *, via driver_cast.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Falk Tannhäuser
2006-02-22 21:06:08 UTC
Permalink
Post by Brian Neal
I do know what I am doing here, and I am taking the authors advice
about not using reinterpret_cast. Using reinterpret_cast here doesn't
feel right, as I am doing nothing that is non-portable. Or at least
nothing that a C programmer would not bat an eye at. :-)
reinterpret_cast is not more or less portable than your
pointer_cast (since both are equivalent), so if it does
the Right Thing for you, why not use it directly, rather
than inventing a new name for it, that any maintenance
coming after you will have to learn in order to understand
your code? Concerning C compatibility, according to § 5.4/5,
a C-style cast is equivalent to reinterpret_cast (possibly
followed by const_cast) unless the conversion in question
can be performed by static_cast (possibly followed by
const_cast).

Falk

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Brian Neal
2006-02-23 10:29:54 UTC
Permalink
Post by Falk Tannhäuser
Post by Brian Neal
I do know what I am doing here, and I am taking the authors advice
about not using reinterpret_cast. Using reinterpret_cast here doesn't
feel right, as I am doing nothing that is non-portable. Or at least
nothing that a C programmer would not bat an eye at. :-)
reinterpret_cast is not more or less portable than your
pointer_cast (since both are equivalent), so if it does
the Right Thing for you, why not use it directly, rather
than inventing a new name for it, that any maintenance
coming after you will have to learn in order to understand
your code?
I disagree with your assertion on reinterpret_cast and the pointer_cast
I proposed being equivalent.

Take a look at this again. We are trying to cast a pointer to one type
of object into a pointer to another type of object with
reinterpret_cast:

T1* p1 = ...;
T2* p2 = reinterpret_cast<T2*>(p1);

Here, according to the standard, the result of such a conversion is
unspecified (!!!).

But, for this type of code:

T1* p1 = ...;
void* vp = p1;
T2* p2 = static_cast<T2*>(vp);

it is specified what should happen here and it doesn't depend on your
compiler's implementation.

At least that is my understanding after reading Sutter & Alexandrescu
and skimming the standard.

That is why I seek to not use reinterpret_cast for this type of
conversion. I am fine with going through a void* and static_casting,
but I just wanted to see it cleaned up a bit. That is why I threw out
the pointer_cast set of functions for comments.


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Falk Tannhäuser
2006-02-25 11:28:35 UTC
Permalink
Post by Brian Neal
Take a look at this again. We are trying to cast a pointer to one type
of object into a pointer to another type of object with
T1* p1 = ...;
T2* p2 = reinterpret_cast<T2*>(p1);
Here, according to the standard, the result of such a conversion is
unspecified (!!!).
... except when it is :-).

While § 5.2.10/7 says effectively:
[...] Except that converting an rvalue 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, the result of such a pointer
conversion is unspecified.

we must not stop reading at this point, because the
guarantee we need for what we want to achieve here is
hidden in the Standard a few lines away, in § 5.2.10/10
(the numbering was added by me):
1° An lvalue 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.
2° That is, a reference cast reinterpret_cast<T&>(x) has
the same effect as the conversion *reinterpret_cast<T*>(&x)
with the built-in & and * operators.
3° The result is an lvalue that refers to the same object as
the source lvalue, but with a different type.
4° No temporary is created, no copy is made, and constructors
(12.1) or conversion functions (12.3) are not called.

So 3° states that the result of the reference cast refers to
_the same object_, and 2° ensures that this guarantee extends
to pointer casts. (Note that 2° makes also possible the
implementation of boost::addressof(), which works even for
objects of a class type having an overloaded unary operator&().)

Well, there is a little problem: 3° does not explain what
"referring to the same object" means when the source lvalue
is not properly aligned to store the target type - but
in case of casting to 'char*' or 'unsigned char*', this
problem doesn't occur.
Furthermore, § 3.10/15 lists the cases where aliasing
is allowed. Accessing the stored value of an object
through an lvalue of type 'char' or 'unsigned char'
is explicitly allowed.

I infer from these Standard clauses that reinterpret_cast
is appropriate when we want to access the object
representation of an object as 'unsigned char' sequence.
I hope I'll be corrected when I'm wrong in this understanding.

Concerning reinterpret_cast, § 9.2/17 is also relevant:
A pointer to a POD-struct object, suitably converted using
a reinterpret_cast, points to its initial member [...]
and vice versa.

Last but not least, as soon as we have to do with POD types,
there is also the argument of C compatibility kicking in.
According to § 5.4/5, given
T1* p1 = ...;

T2* p2 = reinterpret_cast<T2*>(p1);
for unrelated types T1 and T2 is equivalent to
T2* p2 = (T2*)p1; /* (1) */

while
T2* p2 = pointer_cast<T2*>(p1);
is equivalent to
T2* p2 = (T2*)(void*)p1; /* (2) */

I don't imagine any C programmer would expect (1) and (2)
yield different results, and thus I don't imagine a C
programmer would prefer (2) over (1).
Post by Brian Neal
T1* p1 = ...;
void* vp = p1;
T2* p2 = static_cast<T2*>(vp);
it is specified what should happen here and it doesn't depend on your
compiler's implementation.
Is it? While § 4.10/2 (standard pointer conversions) guarantees
The result of converting a “pointer to cv T” to a
“pointer to cv void” points to the start of the storage
location where the object of type T resides [...]

but § 5.2.9/10 (concerning static_cast) says just
An rvalue of type “pointer to cv void” can be
explicitly converted to a pointer to object type.
A value of type pointer to object converted to
“pointer to cv void” and back to the original
pointer type will have its original value.
so it is not explicitly stated what happens
when casting the 'void*' to a pointer with a type
different from the original one. Of course, one may
infer from other parts of the Standard (like § 3.9)
that the Right Thing happens when the alignment is
OK and aliasing rules allow it - which is neither
more nor less than what reinterpret_cast gives you.
Post by Brian Neal
At least that is my understanding after reading Sutter & Alexandrescu
and skimming the standard.
Well, I do not have the book handy, but I imagine their
advice is targeted to application level programmers,
rather than to seasoned low-level hackers (like you
and me :-)) having routinely do do with device drivers,
network stacks and C legacy APIs. Maybe the authors
will enlighten us on the rationale of the advice in question...

Falk

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Loading...