Incompatible type casts

M

m0shbear

I have noticed that there are two basic types of pointers, from which
direct casting (reinterpret_cast) is impossible:
- T C::*
- T *

It looks like the difference between the two is, that in the former,
the pointer is defined as to being attached to a class.
In the latter, such a thing does not exist.

It seems that the compiler refuses to cast between the two because UD
ensues when you detach a pointer from its binding class or vice versa.
This means that any direct casts between the two will fail.
So it seems like the only ways to forcibly set the value for T (C::*x)
(...) are:
1) *reinterpret_cast<void**>(&x)
2) template<C> union pt { void *p; void (C::*q)();}; pt<C> p; p.q =
reinterpret_cast<void (C::*)()>(x);
// do something with p.p
x = reinterpret_cast<typeof(x)>(p.q);

The first one is more elegant but the second one is more useful when
you templatize overloaded cast operator ::*, as T C::* only works on
cast, not on assignment, so a dummy T C::* cannot be created. In this
case, pt<C>::p is set to the value that you want pt<C>::q to be, in
which case reinterpret_cast<T C::*>(pt<C>::q) returns the desired cast
of the value.

I am using this in order to create a proof-of-concept nullptr type
that matches a system's libc NULL, for some weird system that has
reinterpret_cast<int>(NULL) != 0, so that a weird NULL can be returned
as a normal 0 inside a wrapper function.

Which section of the ISO C++ spec defines this forbidden behavior?
Also, in which section of ISO C++ is it stated that any cast from 0 to
a pointer type shall work?
Unlike ISO 9899, the C++ spec's language is simply too complex for me
to read, kind of like the tax law. ISO 9899 had enough ambiguities and
complexities in it :p
 
J

James Kanze

I have noticed that there are two basic types of pointers, from which
direct casting (reinterpret_cast) is impossible:
- T C::*
- T *

There are, in fact, four basic categories of pointers: pointers
to data, pointers to functions, pointers to member data, and
pointers to member functions. Arguably, the last two aren't
really pointers at all, since you can't simply dereference them
and access data or a function. And you can't cast between any
of the categories (unless your compiler has an extension which
supports it).
It looks like the difference between the two is, that in the former,
the pointer is defined as to being attached to a class.
In the latter, such a thing does not exist.

No. The former isn't really a pointer per se; it's not attached
to a class, but rather requires an object of class type in order
to be used.
It seems that the compiler refuses to cast between the two
because UD ensues when you detach a pointer from its binding
class or vice versa.

No. It refuses to convert between the two because the standard
says that such conversions are not allowed. The reason they are
not allowed is that the two "pointers" are in fact two unrelated
things: typically (in all modern implementations), the first is
actually an integer (and might be just a short) with the offset
of the variable within the class; the second is a real pointer.
This means that any direct casts between the two will fail.
So it seems like the only ways to forcibly set the value for T (C::*x)
(...) are:
1) *reinterpret_cast<void**>(&x)

Which will result in undefined behavior.
2) template<C> union pt { void *p; void (C::*q)();}; pt<C> p; p.q =
reinterpret_cast<void (C::*)()>(x);
// do something with p.p
x = reinterpret_cast<typeof(x)>(p.q);
Ditto.

The first one is more elegant but the second one is more useful when
you templatize overloaded cast operator ::*, as T C::* only works on
cast, not on assignment, so a dummy T C::* cannot be created. In this
case, pt<C>::p is set to the value that you want pt<C>::q to be, in
which case reinterpret_cast<T C::*>(pt<C>::q) returns the desired cast
of the value.
I am using this in order to create a proof-of-concept nullptr type
that matches a system's libc NULL, for some weird system that has
reinterpret_cast<int>(NULL) != 0, so that a weird NULL can be returned
as a normal 0 inside a wrapper function.

Why not just use static_cast<int>(NULL)? That should work. For
that matter, reinterpret_cast<int>(NULL) isn't legal C++, and
shouldn't compile; there's nothing in §5.2.10 which allows
a reinterpret_cast between integral types. (Probably an
oversight, but there's also nothing that I can see which allows
conversion between the same types; unless I've overlooked
something said:
Which section of the ISO C++ spec defines this forbidden behavior?

None. You can't define "forbidden" behavior. And of course,
the reason it's forbidden is that there is nothing in the
standard which allows it? I could easily cite a number of
places in the standard where it doesn't say that this conversion
exists, but I'm not sure that would advance anything. It's not
an implicit conversion, because it's not described in section 4;
it's not a legal static_cast, because it isn't described in
section 3.2.9, etc.
Also, in which section of ISO C++ is it stated that any cast
from 0 to a pointer type shall work?

Section 4.10. And not just any cast from 0 will work; it has to
be a integral constant expression evaluating to 0. Thus,
something like:
void* p1 = 0;
int i = 0;
void* p2 = reinterpret_cast<void*>(i);
assert( p1 == p2 );
is not guaranteed to work.
Unlike ISO 9899, the C++ spec's language is simply too complex for me
to read, kind of like the tax law. ISO 9899 had enough ambiguities and
complexities in it :p

The standard isn't always easy to read, but in this case, the
rules are pretty much the same as in C, at least for the
implicit conversions. (C++ has some additional implicit
conversions, e.g. derived to base, but that's about it. And of
course, C didn't have pointers to members. But if you're
thinking "pointers", it's probably best to just consider them
something else, which isn't a "pointer".)
 
R

Ruslan Mullakhmetov

Section 4.10. And not just any cast from 0 will work; it has to
be a integral constant expression evaluating to 0. Thus,
something like:
void* p1 = 0;
int i = 0;
void* p2 = reinterpret_cast<void*>(i);
assert( p1 == p2 );
is not guaranteed to work.

Why is there restriction to _const_ expression? As far as I understood
this sample leads to UB because of i is not a _const_. Am I right?

But I still can't understand why i should be const to make this code
working. Does pointers to data and const expressions (their
representation) evaluating to zero are guaranteed to have the same size?

BR, RM
 
J

Joshua Maurice

Why is there restriction to _const_ expression? As far as I understood
this sample leads to UB because of i is not a _const_. Am I right?

But I still can't understand why i should be const to make this code
working. Does pointers to data and const expressions (their
representation) evaluating to zero are guaranteed to have the same size?

IIRC You are not guaranteed that a null pointer has an object
representation of all 0 bits.

When you (implicitly or explicitly) cast a const integral expression
to a pointer, the compiler first checks if the const integral
expression has value 0. If it does, it then replaces that const
integral expression with the null pointer constant - again, IIRC which
may not have object representation of all 0 bits. It frequently does,
but IIRC it's not required.

If the integral expression is not const, then the compiler cannot
(easily) check its value at compile time. If the value isn't 0, then
this is "sort of" a type error; the assignment shouldn't be allowed,
so we require that it's checked at compile time to enforce this type
restriction. Also, the compiler may need to step in and replace that
integral expression with the null pointer constant, and it can only do
that (easily) for a /const/ integral expression.

Also, I'm not sure of what the standard says offhand, but I wouldn't
use a reinterpret_cast to do this cast. I would use only an implicit
or static_cast to do this cast. reinterpret_cast is there if you know
the underlying object representation scheme of the particular
implementation, not for basic usage like null pointer constants. In
fact, on an odd machine which has a null pointer constant which does
not have object representation of all 0 bits, I would not be surprised
if reinterpret_cast<void*>(0) gave a different result than
static_cast<void*>(0). Again, I'm not sure what the standard says on
this offhand (if anything), but I wouldn't be surprised. It will
surprise other coders who have to maintain your code, so that alone
should be sufficient reason to not use reinterpret_cast<void*>(0) as
the null pointer constant even if it works by standard.
 
J

James Kanze

On 12/16/2010 3:06 PM, James Kanze wrote:
Why is there restriction to _const_ expression? As far as I understood
this sample leads to UB because of i is not a _const_. Am I right?

The example is not guaranteed to work because i is not
a constant integral expression. Making i const would make it
work in this case, but something like "int const i = f()" would
still not make it work.
But I still can't understand why i should be const to make
this code working.

Because the standard says so:). Making it work for non-const
could require in some extra runtime logic on some exotic
platforms. (I would have guessed that no modern platform would
be affected, but the original poster apparently has one.)
Does pointers to data and const expressions (their
representation) evaluating to zero are guaranteed to have the
same size?

Size has nothing to do with it (but pointers and integral types
aren't guaranteed to have the same size). The issue is rather
that a null pointer is not guaranteed to have a representation
which looks like a 0 if the bits are treated as an int. Which
might require special handling if evaluated at runtime.
 
J

James Kanze

[...]
Also, I'm not sure of what the standard says offhand, but I wouldn't
use a reinterpret_cast to do this cast. I would use only an implicit
or static_cast to do this cast.

The only way to convert an int to a pointer (unless the int is
a constant expression evaluating to 0) is to use
a reinterpret_cast.
reinterpret_cast is there if you know
the underlying object representation scheme of the particular
implementation, not for basic usage like null pointer constants.

Exactly. But i is *not* a null pointer constant. It is just
some integer.
In
fact, on an odd machine which has a null pointer constant which does
not have object representation of all 0 bits, I would not be surprised
if reinterpret_cast<void*>(0) gave a different result than
static_cast<void*>(0).

The standard requires "a null pointer value" to convert to
a null pointer value of the target type. (I'm not sure that
a "null pointer constant" is a "null pointer value". Still, I'd
expect reinterpret_cast to handle it as one. Which means that
reinterpret_cast<void*>(0) may not be equal to
reinterpret_cast said:
Again, I'm not sure what the standard says on
this offhand (if anything), but I wouldn't be surprised. It will
surprise other coders who have to maintain your code, so that alone
should be sufficient reason to not use reinterpret_cast<void*>(0) as
the null pointer constant even if it works by standard.

Yes. If you want a null pointer value, don't use
reinterpret_cast.
 
R

Ruslan Mullakhmetov

The example is not guaranteed to work because i is not
a constant integral expression. Making i const would make it
work in this case, but something like "int const i = f()" would
still not make it work.


Because the standard says so:). Making it work for non-const
could require in some extra runtime logic on some exotic
platforms. (I would have guessed that no modern platform would
be affected, but the original poster apparently has one.)


Size has nothing to do with it (but pointers and integral types
aren't guaranteed to have the same size). The issue is rather
that a null pointer is not guaranteed to have a representation
which looks like a 0 if the bits are treated as an int. Which
might require special handling if evaluated at runtime.


Thanks. Never thought that could be much difference beetwen compile
time evaluating and runtime evaluating pointers.

As far as I understood, zero is only a sort of alias for real
representation of null pointer. So if compiler could ensure that
expression is evaluating to zero it could make a substitution to real
null pointer value/representation which lead to no runtime overhead.
Otherwise it could not be done so easy (need to check at runtime if the
value is zero) so no guaranties are provided by the standard.

Am I right?

BR, RM
 
Ö

Öö Tiib

    As far as I understood, zero is only a sort of alias for real
representation of null pointer. So if compiler could ensure that
expression is evaluating to zero it could make a substitution to real
null pointer value/representation which lead to no runtime overhead.
Otherwise it could not be done so easy (need to check at runtime if the
value is zero) so no guaranties are provided by the standard.

    Am I right?

Almost. ;) Compilers have to diagnose it (as erroneous or non-
standard) if you assign some unknown-at-compile-time-int or known-at-
compile-time-42 to pointer, because there is no implicit conversion.
Compilers accept only what they know to be 0.
 
J

James Kanze

Thanks. Never thought that could be much difference beetwen compile
time evaluating and runtime evaluating pointers.

There isn't any difference, really. It's just that a "constant
integral expression evaluating to 0" is a special case (at
compile time). Any other value, or anything that means that the
expression isn't formally constant (even if it is in practice,
and the compiler can see and know this), and there's no
difference.
As far as I understood, zero is only a sort of alias for real
representation of null pointer.

If it is a constant, and the conversion takes place. The
standard speaks of a "null pointer constant" (which despite the
name isn't a pointer, but must have integral type). But the
type of 0 remains int unless there is a context which requires
the conversion. In which case, if the 0 is the result of a
constant expression (in the sense of the standard), the 0 is
converted (at compile time) to whatever representation a null
pointer has. If the expression isn't constant, or the value
isn't 0, it's an error---there is no implicit integral type to
pointer conversion.
So if compiler could ensure that
expression is evaluating to zero it could make a substitution to real
null pointer value/representation which lead to no runtime overhead.
Otherwise it could not be done so easy (need to check at runtime if the
value is zero) so no guaranties are provided by the standard.
Am I right?

Not totally. First, it's not a question of what the compiler
can ensure, but of the formal definition of an "integral
constant expression". A lot of compilers can ensure things in
cases where the standard doesn't consider it an "integral
constant expression" (E.g. numeric_limits<int>::max() -
numeric_limits<int>::max(). numeric_limits<int>::max is
almost certainly an inline function, and most compilers will be
able to determine that the results of the subtraction must be
zero. But as far as the standard is concerned, it's not an
"integral constant expression".) And secondly, the standard
specifies exactly the behavior regardless: if you need a
pointer, and you have an integral expression, and it's either
not const, or it doesn't evaluate to zero, it's an error.

If you use reinterpret_cast, of course, all bets are off. The
pointer resulting from reinterpret_cast'ing an int to a pointer
is implementation defined (but intended to be "unsurprising to
someone familiar with the architecture of the machine").
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

function casts 27
Casts 81
which of these 3 casts would you prefer? 47
operator= could not be generated 6
Lexical Analysis on C++ 1
Pointer casts for OOP 2
incompatible pointer assignment 7
incompatible pointer type 13

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,072
Latest member
trafficcone

Latest Threads

Top