Type-checking casts for GNU C

J

Jan Engelhardt

---------- Forwarded message ----------
Date: Tue, 23 Dec 2008 20:54:19 +0100 (CET)
To: gcc-help ML
Subject: Type-checking casts for GNU C

To the C-programming community,


this is word to let the world know that I have found a way to make the
C++ new-style casts, specifically static_cast<> and const_cast<>, in
GNU C (that's right, not C++).

I had seen annotation-only "casts" in C projects (and not just my own)
before. Just grepping -Pin '\w+_cast\(' in all .tar.gz/.tar.bz2 in just
a bunch of .src.rpms ([gklmnop]* was my run) of the distro already
reveal quite a bit. It looks like

---<8--- from libjasper
/* The below macro is intended to be used for type casts. By using this
macro, type casts can be easily located in the source code with
tools like "grep". */
#define JAS_CAST(t, e) \
((t) (e))
--->8---

---<8--- from mc (midnight commander)
/* C++ style type casts */
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
--->8---

Essentially it boils down to just the plain old cast, and it does not do
much except the annotation -- you can still have Freudian slips with
these.

But the presence of the ARRAY_SIZE/BUILD_BUG_ON macros in the Linux
kernel source([1]) inspired me to try something similarly with GCC's
extensions, for fun and profit.

[1] http://lwn.net/Articles/226007/

Result are some macros that actually _do_ type-checking instead of just
annotation and giving something to grep for. (Trying to reach for the
art market.) I am not aware of this having been done before, as it is
pretty impossible to grep or google for such without turning up lots of
C++ code; it is further my assumption that since the GCC code written by
those who understand it thoroughly, but which only uses "CONST_CAST" in
annotation form only without type checking, that my approach could
indeed be novel.

The reference implementation is in libHX starting from 2.0.
Linux distro packages will be doing an update soon.

Direct view of defs.h: http://tinyurl.com/7grobb
FM project page with URLs: http://freshmeat.net/projects/libHX/

It is recommended to have a read at the documentation (reachable
via the "Demo" link on the FM project page) to get the idea of
the numbered const_castN.
 
C

c prog fan

Jan said:
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
[...]

Hmm, isn't

#ifndef C_plus_plus
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif /* C_plus_plus */

better ? ( assuming you define a flag for C/C++ usage )

But then if I want to compile code with C++ specific syntax I may use a
C++ compiler as well ; you know , those different casts are there for a
reason anyway ...

Regards.
 
J

jameskuyper

c said:
Jan said:
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
[...]

Hmm, isn't

#ifndef C_plus_plus
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif /* C_plus_plus */

better ? ( assuming you define a flag for C/C++ usage )

Not really. I doubt that this code was intended to be used with C++,
but if it were, then the appropriate definition would be

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif
 
C

c prog fan

jameskuyper said:
c said:
Jan said:
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
[...]

Hmm, isn't

#ifndef C_plus_plus
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif /* C_plus_plus */

better ? ( assuming you define a flag for C/C++ usage )

Not really. I doubt that this code was intended to be used with C++,
but if it were, then the appropriate definition would be

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif

Can we use
#const_cast(m_type, m_expr) ((const m_type) (m_expr))
for C case ?
 
I

Ian Collins

c said:
jameskuyper said:
c said:
Jan Engelhardt wrote:

#define const_cast(m_type, m_expr) ((m_type) (m_expr))
[...]

Hmm, isn't

#ifndef C_plus_plus
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif /* C_plus_plus */

better ? ( assuming you define a flag for C/C++ usage )

Not really. I doubt that this code was intended to be used with C++,
but if it were, then the appropriate definition would be

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif

Can we use
#const_cast(m_type, m_expr) ((const m_type) (m_expr))
for C case ?

You could if you define it right. The problem would be it only works
one way (to const). The original follows the C++ const_cast (to/from
const).
 
C

c prog fan

Ian said:
c said:
jameskuyper said:
c prog fan wrote:
Jan Engelhardt wrote:

#define const_cast(m_type, m_expr) ((m_type) (m_expr))
[...]

Hmm, isn't

#ifndef C_plus_plus
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif /* C_plus_plus */

better ? ( assuming you define a flag for C/C++ usage )
Not really. I doubt that this code was intended to be used with C++,
but if it were, then the appropriate definition would be

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif
Can we use
#const_cast(m_type, m_expr) ((const m_type) (m_expr))
for C case ?

You could if you define it right. The problem would be it only works
one way (to const). The original follows the C++ const_cast (to/from
const).

Do you mean this ?

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((const m_type) (m_expr))
#endif
 
J

Jan Engelhardt

Jan said:
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
[...]

Hmm, isn't

#ifndef C_plus_plus
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif /* C_plus_plus */

better ? ( assuming you define a flag for C/C++ usage )


What you quoted from me is the example from Midnight Commander.
Because that is a C program, it does not have to deal with #ifdef
__cplusplus at all.
(But has this relevance for libHX/defs.h?)
 
J

jameskuyper

c said:
Ian said:
c said:
jameskuyper wrote:
c prog fan wrote:
Jan Engelhardt wrote:

#define const_cast(m_type, m_expr) ((m_type) (m_expr))
[...]

Hmm, isn't

#ifndef C_plus_plus
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif /* C_plus_plus */

better ? ( assuming you define a flag for C/C++ usage )
Not really. I doubt that this code was intended to be used with C++,
but if it were, then the appropriate definition would be

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif
Can we use
#const_cast(m_type, m_expr) ((const m_type) (m_expr))
for C case ?

You could if you define it right. The problem would be it only works
one way (to const). The original follows the C++ const_cast (to/from
const).

Do you mean this ?

The C++ const_cast<>() operator, Jan Engelhardt's const_cast()
function-like macro, and my re-write of that macro all share one
common feature: they can be used to either add or remove 'const';
which one it does is controlled by whether or not the type you specify
is a const-qualified type. Your version of the function-like macros
adds 'const', whether or not the specified type already has it, and it
cannot be used to remove 'const'.
 
I

Ian Collins

c said:
Do you mean this ?

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((const m_type) (m_expr))
#endif

Well you could, but as I said, it only works one way. So the macro has
different meanings in the two languages.

C++ const_cast is used to cast to and from const, which is what James
posted:

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif
 
C

c prog fan

Ian said:
Well you could, but as I said, it only works one way. So the macro has
different meanings in the two languages.

C++ const_cast is used to cast to and from const, which is what James
posted:

#ifdef __cplusplus
#define const_cast(m_type, m_expr) const_cast<m_type>(m_expr)
#else
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
#endif

Thanks so I misunderstood you on this. But still it sounds strange to me
to use one style of casting to various C++ casts. Since I expect them to
be different under specific situations like compile time vs run time etc
.. And my post was just to find more suitable casts for them. Still for
my learning I would be glad to see those casts if anyone wants to come
with them.
 
C

c prog fan

Jan said:
---------- Forwarded message ----------

Hmmm ...
Date: Tue, 23 Dec 2008 20:54:19 +0100 (CET)
To: gcc-help ML
Subject: Type-checking casts for GNU C

Yes let the people deal with "GNU C" etc no problem ...
To the C-programming community,

Really ?
this is word to let the world know that I have found a way to make the
C++ new-style casts, specifically static_cast<> and const_cast<>, in
GNU C (that's right, not C++).
yes that's just right neither C++ nor C.
I had seen annotation-only "casts" in C projects (and not just my own)
before. Just grepping -Pin '\w+_cast\(' in all .tar.gz/.tar.bz2 in just
a bunch of .src.rpms ([gklmnop]* was my run) of the distro already
reveal quite a bit. It looks like

http://en.wikipedia.org/wiki/Grep

---<8--- from libjasper
/* The below macro is intended to be used for type casts. By using this
macro, type casts can be easily located in the source code with
tools like "grep". */
#define JAS_CAST(t, e) \
((t) (e))
--->8---

---<8--- from mc (midnight commander)
/* C++ style type casts */
#define const_cast(m_type, m_expr) ((m_type) (m_expr))
--->8---

Essentially it boils down to just the plain old cast, and it does not do
much except the annotation -- you can still have Freudian slips with
these.

But the presence of the ARRAY_SIZE/BUILD_BUG_ON macros in the Linux
kernel source([1]) inspired me to try something similarly with GCC's
extensions, for fun and profit.

[1] http://lwn.net/Articles/226007/

Really thanks : macros in the Linux kernel source then GCC's extensions ...
Result are some macros that actually _do_ type-checking instead of just
annotation and giving something to grep for. (Trying to reach for the
art market.) I am not aware of this having been done before, as it is
pretty impossible to grep or google for such without turning up lots of
C++ code; it is further my assumption that since the GCC code written by
those who understand it thoroughly, but which only uses "CONST_CAST" in
annotation form only without type checking, that my approach could
indeed be novel.

Sure your "approach" is novel here ...
The reference implementation is in libHX starting from 2.0.
Linux distro packages will be doing an update soon.

Again Linux and alike.
Direct view of defs.h: http://tinyurl.com/7grobb
FM project page with URLs: http://freshmeat.net/projects/libHX/

It is recommended to have a read at the documentation (reachable
via the "Demo" link on the FM project page) to get the idea of
the numbered const_castN.

Thanks for more OS and implementation specific material and links.

Please Note : I usually don't want to come like this but since you
mentioned the relevance of my post please first show how much your
original post has relevance here.
 
J

James Kuyper

c said:
Thanks so I misunderstood you on this. But still it sounds strange to me
to use one style of casting to various C++ casts. Since I expect them to
be different under specific situations like compile time vs run time etc

I'm not quite sure what "one style" you're referring to? C does indeed
have only one style, but you explicitly referred to C++ casts. C++ has
five styles (static_cast, const_cast, dynamic_cast, reinterpret_cast,
explicit cast (functional notation) and explicit cast (cast notation).
Each of those casts does have different features, different contexts
where they can be used, and different effects.

The cast-notation version can do almost everything that any of the
others can do, and a couple of additional things as well. It is
syntactically identical, and semantically quite similar, to the only
kind of cast that C has.

There's really nothing you can do with a macro like this to emulate in C
the distinctions that C++ makes; it must always come down to a
cast-notation cast in the end. Therefore, macros like this seem pretty
pointless to me unless they are intended to be used by code that is
expected to be compiled by both a C compiler and a C++ compiler.
 
C

c prog fan

James said:
I'm not quite sure what "one style" you're referring to? C does indeed
have only one style, but you explicitly referred to C++ casts. C++ has
five styles (static_cast, const_cast, dynamic_cast, reinterpret_cast,
explicit cast (functional notation) and explicit cast (cast notation).
Each of those casts does have different features, different contexts
where they can be used, and different effects.

The cast-notation version can do almost everything that any of the
others can do, and a couple of additional things as well. It is
syntactically identical, and semantically quite similar, to the only
kind of cast that C has.

There's really nothing you can do with a macro like this to emulate in C
the distinctions that C++ makes; it must always come down to a
cast-notation cast in the end. Therefore, macros like this seem pretty
pointless to me unless they are intended to be used by code that is
expected to be compiled by both a C compiler and a C++ compiler.

Ok I agree on this and I meant one style cast of C. My point is just
that a simple macro may not cover all what C++ casts do in practice.
 
J

jameskuyper

c prog fan wrote:
....
Ok I agree on this and I meant one style cast of C. My point is just
that a simple macro may not cover all what C++ casts do in practice.

A simple C macro uses a C-style cast can perform all of the
conversions that the C++ casts can perform that have any meaning in C.
The advantage of the C++ casts lies not in what they can do, but in
what they can't. For each of the three named C++ casts, there are a
large number of conversions that, if you attempt them with that
particular cast, will produce a mandatory diagnostic message, and will
probably prevent the compiler from accepting them. This is a good
thing, and not something that any C macro can give you.
 
C

c prog fan

jameskuyper said:
A simple C macro uses a C-style cast can perform all of the
conversions that the C++ casts can perform that have any meaning in C.
The advantage of the C++ casts lies not in what they can do, but in
what they can't. For each of the three named C++ casts, there are a
large number of conversions that, if you attempt them with that
particular cast, will produce a mandatory diagnostic message, and will
probably prevent the compiler from accepting them. This is a good
thing, and not something that any C macro can give you.

I particularly want to ask these :

1. Are there C++ casts that can operate on compile time rather than
runtime ?
2. Can C cast operate on compile time with simple methods ?

Now if the answers is yes then no we might consider more sophisticated
ways to approximate them ( if that is really needed of course ).
 
H

Harald van Dijk

/* Casts expr to type T. Generates compile error if anything other than
constness of pointed-to type is changed. Doesn't work with void*. */
#define CONST_CAST(T,expr) \
(sizeof(*(T*) 0 == (expr) && *(T) 0 == *(expr)) ? \
(T) (expr) : (T) (expr))

This is a nice idea, but this also allows conversions between int * and
volatile int *, and doesn't allow conversions between struct S * and
const struct S *.
 
J

jameskuyper

c said:
I particularly want to ask these :

1. Are there C++ casts that can operate on compile time rather than
runtime ?
2. Can C cast operate on compile time with simple methods ?

Keep in mind that the C and C++ standards don't distinguish between
compile time and run-time; they only talk about translation and
execution of a program. A conforming implementation could produce an
executable file that contains a copy of the source code files, and
doesn't actually perform any of the translation phases on those files
until just before each execution of the program.

If the compiler is able to predict with a certainty what the argument
of a given cast operator will be, before the program has even started
executing, it may be able to evaluate it during translation. In
general, this requires that the argument of the cast be a constant
expression, but a sufficiently smart compiler can look at an
algorithm, pre-determine what the result of applying that algorithm
will be, and optimize the program by just generating code for that
result, without actually generating any code for executing the
algorithm.

Some casts are allowed inside of what C calls "integer constant
expressions", and what C++ calls "integral constant expressions" (I'll
just call them ICE's). An ICE that occurs in a context where an ICE is
required (contexts such as array lengths, case expressions, etc.) must
be evaluated during translation.

Casts involving pointers and references cannot be evaluated until the
things those pointers and references refer to have been given actual
memory locations. Casts involving floating points values can be
problematic to perform during compilation if cross-compiling for a
platform with floating point hardware that is different from that used
by the compiler.

What can occur during translation, with either language, is detection
of the fact that a given use of cast operator may make issuance of a
diagnostic message by the compiler mandatory. The difference between
the two languages is that diagnostics are mandatory for the named C++
casts under a much wider variety of circumstances than C casts. No
macro wrapper for a C cast can simulate that feature of C++.
 
H

Harald van Dijk

Which is what C++'s const_cast also allows. The point is to change the
const/volatile qualifiers without changing anything else.

In that case, the comment needs updating if volatile changes are supposed
to be accepted.
I had an alternate version that would work for structs as well:

#define CONST_CAST(T,expr) \
(sizeof(&**(T*) 0 == &*(expr)) ? (T) (expr) : (T) (expr))

The only difference between &*x and x (when both are valid) is that the
former is not an lvalue. Since lvalue-ness is not relevant here, this
means simply

#define CONST_CAST(T,expr) \
(sizeof(*(T*) 0 == (expr)) ? (T) (expr) : (T) (expr))
But for some dumb reason, gcc (4.0) was accepting it without error when
expr and/or T was a void*. I had warnings and pedantism at maximum too.

Yes, that is the right thing for gcc to do here. C allows you to compare a
void * and a struct S * without any explicit conversion.

Here's one other possibility:

#define CONST_CAST(T,expr) \
(sizeof *(T) 0, \
sizeof *(expr), \
sizeof((T) 0 == (expr)), \
(T) (expr))

....but it would fail at least for pointers to arrays of unknown length.
 
I

Ian Collins

blargg said:
If you want portable code for ANY compiler, not just GCC, here are some
I just whipped up that mimic C++'s static_cast and const_cast. I'm sure
there are some obscure cases they don't catch; maybe others can identify
and correct these.

/* Casts expr to type T. Generates compile error if the reverse
conversion can't be done implicitly. */
#define CAST(T,expr) \
(sizeof(*(T*) 0 == (expr)) ? (T) (expr) : (T) (expr))

static void test_cast( void* v, int* p, int i, float f )
{
/* legal */
p = CAST(int*,p);
p = CAST(int*,v);
v = CAST(void*,p);
v = CAST(void*,v);
i = CAST(int,i);
i = CAST(int,f);
f = CAST(float,i);
f = CAST(float,f);

/* compile-time diagnostic */

Al of the following assignments will produce a diagnostic without the
macros.
 
H

Harald van Dijk

#define CONST_CAST(T,expr) \
(sizeof(&**(T*) 0 == &*(expr)) ? (T) (expr) : (T) (expr))

The only difference between &*x and x (when both are valid) [...]

Sorry, I had missed the point completely here. It is of course to catch
the cases where &*x is not valid -- to check whether T is a pointer type.
 

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


Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top