M
mathog
{ Introductory material, skip down to the end bracket if you don't
care how I got onto this subject.
I have recently been taking some heat for using constructs like this:
float some_float;
/* code sets some_float */
int i = (int) round(some_float);
in C++ code. Some programmers claim that if it is not rewritten as
int i = static_cast<int> round(some_float);
the sky will fall. So far none of the folks claiming this
have actually presented an example where using C style casts on these
sorts of simple data types actually does something unexpected. They
have a point where inheritance is involved, but there is none of that in
the code in question. I like () better here because it doesn't waste 11
characters on every cast, which becomes a factor if more than one cast
must fit on the same line.
}
(For everything below here assume that float and int are the same size.)
This got me thinking about C casts, where
int i = (int) round(some_float); /* 1a */
specifies a type conversion, whereas this
int i = *(int *)(&some_float); /* 2a */
specifies a reinterpretation of the bits in memory. I always thought
the second form was a pretty awkward way to accomplish this, and that
there was room for improvement there. It isn't that the meaning is not
clear, it is just that having to resort to using a pointer to the data
in order to get its bits reinterpreted (still) feels like a kludge.
There is also "const" and "volatile" to consider, which are fine in
declarations but have always seemed too lengthy when placed inside casts.
So bear me with me and consider this more generalized form for a C cast
(obviously this is entirely hypothetical, there are no compilers that do
this):
([task] [access] type)
Where type must be specified and task and access are optional,
and:
Task specifies the action of the cast, one of:
to Short for "convert to". The target is to be
converted to the type specified.
Default when no [task] is present.
as Short for "use as". The target's bits in memory
are to be interpreted as specified by this cast.
Access specifies read/write access to the result of the cast one of:
rw read/write access.
Default when no [access] is specified.
(Is there currently a keyword to specify this?)
ro read only access (equivalent to "const")
wo write only access (equivalent to "volatile")
Type is the "output" type of the cast, like int or double, just
as in current C casts.
The general form allows these alternate casts:
int i = (int) round(some_float); /* 1a */
int i = (to int) round(some_float); /* 1b */
int i = (to rw int) round(some_float); /* 1c */
int i = *(int *)(&some_float); /* 2a */
int i = (as int) some_float; /* 2b */
const int i = *(const int *)(&some_float); /* 3a */
const int i = (as ro int)some_float; /* 3b */
int i = (int) some_float; /* 4a */
int i = (to int) some_float; /* 4b */
function((volatile float *)&some_float); /* 5a */
function((wo float *)&some_float); /* 5b */
Not a big win in clarity for 1b or 1c vs. 1a, since the extra
text is just specifying defaults. 1b might be a bit clearer
for a beginner, but after a few weeks those training wheels
would come off. I think 2b is clearer than 2a, and 3b clearer
than 3a. Incorrect code, with the wrong task employed, might
be easier to spot if the 4b and 2b forms were uniformly employed, but
otherwise not a clean win for 4b over 4a. 5b versus 5a feels like a wash.
Is something along these lines worth incorporating into the C language,
or no? Clearly we can get along without it - I am just wondering if
adding this would be beneficial.
Regards,
David Mathog
care how I got onto this subject.
I have recently been taking some heat for using constructs like this:
float some_float;
/* code sets some_float */
int i = (int) round(some_float);
in C++ code. Some programmers claim that if it is not rewritten as
int i = static_cast<int> round(some_float);
the sky will fall. So far none of the folks claiming this
have actually presented an example where using C style casts on these
sorts of simple data types actually does something unexpected. They
have a point where inheritance is involved, but there is none of that in
the code in question. I like () better here because it doesn't waste 11
characters on every cast, which becomes a factor if more than one cast
must fit on the same line.
}
(For everything below here assume that float and int are the same size.)
This got me thinking about C casts, where
int i = (int) round(some_float); /* 1a */
specifies a type conversion, whereas this
int i = *(int *)(&some_float); /* 2a */
specifies a reinterpretation of the bits in memory. I always thought
the second form was a pretty awkward way to accomplish this, and that
there was room for improvement there. It isn't that the meaning is not
clear, it is just that having to resort to using a pointer to the data
in order to get its bits reinterpreted (still) feels like a kludge.
There is also "const" and "volatile" to consider, which are fine in
declarations but have always seemed too lengthy when placed inside casts.
So bear me with me and consider this more generalized form for a C cast
(obviously this is entirely hypothetical, there are no compilers that do
this):
([task] [access] type)
Where type must be specified and task and access are optional,
and:
Task specifies the action of the cast, one of:
to Short for "convert to". The target is to be
converted to the type specified.
Default when no [task] is present.
as Short for "use as". The target's bits in memory
are to be interpreted as specified by this cast.
Access specifies read/write access to the result of the cast one of:
rw read/write access.
Default when no [access] is specified.
(Is there currently a keyword to specify this?)
ro read only access (equivalent to "const")
wo write only access (equivalent to "volatile")
Type is the "output" type of the cast, like int or double, just
as in current C casts.
The general form allows these alternate casts:
int i = (int) round(some_float); /* 1a */
int i = (to int) round(some_float); /* 1b */
int i = (to rw int) round(some_float); /* 1c */
int i = *(int *)(&some_float); /* 2a */
int i = (as int) some_float; /* 2b */
const int i = *(const int *)(&some_float); /* 3a */
const int i = (as ro int)some_float; /* 3b */
int i = (int) some_float; /* 4a */
int i = (to int) some_float; /* 4b */
function((volatile float *)&some_float); /* 5a */
function((wo float *)&some_float); /* 5b */
Not a big win in clarity for 1b or 1c vs. 1a, since the extra
text is just specifying defaults. 1b might be a bit clearer
for a beginner, but after a few weeks those training wheels
would come off. I think 2b is clearer than 2a, and 3b clearer
than 3a. Incorrect code, with the wrong task employed, might
be easier to spot if the 4b and 2b forms were uniformly employed, but
otherwise not a clean win for 4b over 4a. 5b versus 5a feels like a wash.
Is something along these lines worth incorporating into the C language,
or no? Clearly we can get along without it - I am just wondering if
adding this would be beneficial.
Regards,
David Mathog