On 23/01/13 23:07, Lawrence Crowl wrote:
On 1/23/13, Jonathan Wakely <jwakely....@gmail.com> wrote:
On 23 January 2013 09:15, Alec Teal wrote:
I was fearful of using the word attribute for fear of getting it wrong?
What
is "this part" of the compiler called
I think attributes are handled in the front end and transformed into
something in the compiler's "tree" data structures.
FWIW I've usually seen this feature referred to as "strong typedefs".
That brings to mind the "strong using" extension G++ had for
namespaces, which (prior to getting standardised as "inline
namespaces") used __attribute__((strong)) so that attribute already
exists in the C++ front end:
http://gcc.gnu.org/onlinedocs/gcc/Namespace-Association.html
Note that there is a proposal before the C++ standard committee on
just this topic. Consider its semantics when implementing.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3515.pdf
After reading that it doesn't seem like the same thing, they are talking
about - essentially - creating "classes" to handle types with no runtime
overhead, they want to be certain the optimizer got rid of it or save
the optimizer a job. I'm not saying that's bad I just think it is
separate. Typdefs are supposed to be an alias, yes in light of that the
title of "type definition" seems a little misleading when put with that
paper, but none the less a typedef is an alias.
While reading the book "The design and evolution of C++" (I am not
saying this to throw around "look, from the 'founder' of C++'s mouth!",
I read it so I could learn why things are the way they are and errors
which would have happened if things had of happened differently, wow
that's a weird sentence, I mean failed attempts) I did enjoy reading
about the strict typing that C++ introduced and that was to catch errors.
The paper has a point, you would never try to multiply two game scores,
so why have compiler tell you if you do? You'd surely mean it! What if
you want to computer the geometric mean (or something, go with me)
ultimately it's still an int! Why not go a step further and give the
compiler the concept of a group (algebraic structure see [1]) this could
stop divisions by zeros! Why not say you can no longer divide an integer
by another integer? MOST of the time the result wont be an integer, with
their example of cartesian(3) to spherical coordinates you shouldn't
have to rigidly define such stuff that it throws an error when you do
something as silly as mix them up and again, when they define x there is
no one-size-fits-all definition for how x operates with other types. If
you were to seriously try you'd just get so much spam, the only reason
the idea doesn't totally implode is because you can "cast" back to the
"base", so if all your definitions - which are more like conditions -
don't allow something, you just side-step it, this is an own goal the
point was you would never sidestep.
There is a line between what I am proposing and what they are, I cannot
define exactly where it is, by their own admitance they cant:
/"This issue has been one of the consistent stumbling blocks in the
design of an opaque typedef//
//facility. In particular, we have come to realize that there is no one
consistent approach to the//
//return type issue such that it will meet all expectations under all
circumstances//:"/
If they could this would be far more important than a proposed C++
feature and would have been 'discovered' during the Abstract Algebra
boom during the times of Gauss.
Back on topic!
I always thought of a hard-typedef as an extension of this. I don't want
it treated like a class, I don't want this to be valid:
----------------------------------------
hard typdef int BookId;
int x = 5;
BookId my_book = x; //should fail
BookId alternate = (BookId) x; //fine - no runtime overhead because it's
just an alias, no compiling overhead really either.
----------------------------------------
This (in the world of classes) is interesting because if anything int is
the parent of BookId, but I shouldn't need a constructor or a
reinterpret_cast (however it'd be applicable) because I had written
"(BookId)" before the x, and that's probably not an accident the
compiler should realize I meant to do it.
Now what about the other way?
----------------------------------------
hard typdef int BookId;
BookId x = 5;
int my_book = x; //should fail
int alternate = (int) x; //fine - no runtime overhead because LOOK!
----------------------------------------
Now in terms of inheritance we have it going both ways, so while you
could look at this as typdef system as "a set of classes all storing one
member of the same type with a different set of operations" you cannot
have implicit conversions both ways! It just makes no sense, yes it can
stop you writing stupid things anyway, but what use is there in defining
"denominator" to be nonzero or what about composite types I would never
want to multiply a real by an imaginary except inside the complex number
multiply function, what if I just want to deal with the imaginary part,
should I use their system and create this new imaginary type to be a
single number.... so forth, the document does talk about this and uses
the words "public" "private" and "protected" to talk about how to
restrict what can be done, just no, take this quote:
"The programmer can similarly define a trampoline with = delete whenever a
particular combination of parameter types ought be disallowed."
In their example they delete the * operation from their 'opaque typedef'
for "energy"
energy something = (energy) ((double) e1* (double) e2);
Just... no, that works around the thing they tried to do and looks awful!
That lack of one true way, the lack of "grand unified theory", and
ultimately don't forget GIGO, if you write stuff that swaps vector
components back it wont work. You can see how I struggle to define my
own line, C++'s stuff over C is not "one size fits all" but there's very
little specialization, where as the above allows for stupid amounts of it
Back on topic!
Solution! No implicit 'casts'!
You could define implicit casts to be one way (up or down if you will)
or only with certain data-types to make writing stuff look a bit nicer
and involve less key-strokes but it can quickly become daft, one of the
other things I like about C++ is a quote from it's 'founder' "if it's an
ugly thing to do writing it should look ugly" or something to that
effect (talking about the various casts), so suppose you have this:
Just generic db access stuff - how one might do it now (you could use a
struct, just go with it, this isn't about best practices in /that/
sense), strings are database field names.
-------------------------------------
int book = cursor.get_int("BookId");
int out_to = cursor.get_int("MemberId")
-------------------------------------
This should not be allowed, ERROR by default, not warning, that's the
point:
-------------------------------------
hard typdef int BookId;
hard typdef int MemberId;
BookId book = cursor.get_int("BookId");
MemberId member = cursor.get_int("MemberId");
-------------------------------------
as it involves implicit casts, it looks nicer yes but it is still an int
to a BookId, also BookId looks much better in my IDE font, I've
developed my own styles over the years (working alone) please don't take
this as "CamelCase? This bitch isn't worth listening to, I know C(++)
people prefer _ but I'm so used to types being capital first... yeah,
I'll learn! Promise!
This should be fine:
-------------------------------------
hard typdef int BookId;
hard typdef int MemberId;
BookId book = (BookId) cursor.get_int("BookId");
MemberId member = (MemberId) cursor.get_int("MemberId");
-------------------------------------
and going the other way (this is pure pseudo-C++)
-------------------------------------
values.add("someInt",(int) someHardTypedefedTypeOfAnInt);
db.insert("table",values);
-------------------------------------
*Note about ugliness:
*The point is that when you do this 'cast' you mean it, this stops you
overloading (next thing ) you shouldn't have to type a lot though just
something to tell the compiler "I mean to write this".
I recently (and loved!) creating a scripting language for my work (mass
backspace, too far off topic even for me!) that had some static typing
(dynamic if unspecified) it wasn't really a speedup, it was very Lua
like in syntax but arrays started at 0, were not maps (there was a map)
and != was there instead of Lua's annoying ~=, anyway, rather than
writing "BookId x = (BookId) some_int;" I just wanted to tell the
compiler "I am aware that I am doing this" and I used "(~)" it parsed
this as a cast, I'd like to use this, so you could (if this were C++
now) write:
-------------------------------------
hard typdef int BookId;
hard typdef int MemberId;
BookId book = (~) cursor.get_int("BookId");
MemberId member = (~) cursor.get_int("MemberId");
-------------------------------------
I'll be honest, the main reason for this was my OCD powers of
doing-things-I-know-are-silly-or-else-cry so I could continue using tab
to align the equals signs without some variable length thing after. But
did it work or what!
*Casts to things it's (the hard-typedef) not an alias of
*Well this is easy, the exact same way as the "base" type, keeping with
my BookId and MemberId as ints:
float f = (~) some_book; //error, for so many reasons, (~) relies on the
relation of equality between hard-typedefs ONLY, there is no reason for
the compiler to 'replace' (~) with (int) or (MemberId)
//if anything the error should be generated from falling back to the
base type, will explain more shortly.
float f = (float) some_book; //fine, you must intend a cast
float f = (float) (~) some_book; //redundant, equiv to the above, but
still fine. - technically this is the correct way but as mentioned, a
cast is clearly intended in the above statement.
as you know the float-int cast normally looks like this:
float f = (float) i;
HOWEVER there is no cast from a MemberId to a float, do what is
expected, be a normal typdef, you may think "Ah! Alec this voids your
own rule of implied types" but wait, sort of yes! But a cast is CLEARLY
intended.
Now what about the other way?
int i = f; //f is some float
is truncating, we all know this, this is an implicit thing.
consider two hard typedefs now, of length, area and volume (3
real-valued units) this does seem kind of like the paper I'm not very
happy with and this type information should usually be carried in the
variable name but I can't think of any other examples, anyway:
the trick here is to realise that it's not about being pedantic, length
l = 0.5; is unambiguous, this behavior will not always apply, remember
again that (~) is short hand for "any possible cast that'd work within
this group of hard-typedefs"
length l = 5.0; //yes I know of the implicit cast from double to float
AND THEN to length, but it's a constant this is therefore fine, I could
formally define this but c'mon, that's just being a total pedant
(required later, but not now)
volume v = (~) l; //fine
volume v = (float) l; //NOT fine the point is volume is not a float
int k = v; //NOT FINE, we don't know that truncation makes sense on this
new type (see that line I talked about earlier?) only that it makes
sense for a float to be truncated
int k = (~) v; //fine
int k = (int) v; //fine because you clearly intend to cast.
int k = (int) (~) v; //fine, technically correct
int k = (int) (float) v; //what the above will "become"
int k = (int) (float) (length) (~) (~) (length) (float) (volume) (float)
v; //POINTLESS, but still valid, there is no 'correct' (~) as it just
means "one that exists that'd work here"
*Is there a hierarchy of typedefs?*
obviously not, but it's worth thinking about why.
hard typedef int BookId;
hard typedef BookId MemberId;
hard typedef int AutherId;
hard typedef RequestId BookId;
Totally valid!
int
+--BookId
| +--MemberId
| +--RequestId
+--AutherId
Is the hierarchy, if any, but:
type(MemberId) = type(BookId) = type(int)
by the equivalence relation of equality this implies, if this is true then:
type(MemberId) = type(int)
so now:
int
+--BookId
| +--RequestId
+--MemberId
+--AutherId
is the same heirarchy, this is not true of class-inheritance btw incase
anyone is thinking "wait a minute" (inheritance is more like a<b<c so
a<c, it DOES distinguish between parent-child and sibling relationships!)
This does not distinguish between parent-child relationships (up/down
the hierarchy 'tree') and sibling relationships (same parent), infact
int isn't really the parent, it just happens to be the only one with
operations defined to allow it to interact with other types.
This means that (~) can expand to any hard-typedef of it's incestuous
hierarchy (I don't have a word yet, group?)
*If there exists a (~) it will be unique, you will never need to use two
casts directly after each other when 'traversing' a typedef group when
searching for a 'cast' to one known type*- can be proved formally
Works great for assignments, operator overloading (consider a definition
for whatever(int); whatever(MemberId); whatever(BookId); how do you pick
one if I say whatever((~)something);)
again:
float f = (float) some_book;
is just shorthand for:
float f = (float) (int) some_book;
which is the same as:
float f = (float) (~) some_book;
as the cast is clearly intended (you WANT a float) you may use the first
one given, the point is to be sure you mean to, not to be annoying.
*Overloading behavior
*EXACTLY as typedef BUT may be overloaded - unlike a typedef.
that means:
hard typedef int ArrayFlag
ArrayFlag APPEND = 0;
ArrayFlag START = 1;
template<T> T& MyArray::operator[](ArrayFlag flag);
template<T> T& MyArray::operator[](unsigned int pos); //normal
array-like access.
Now you can do:
MyArray<int> array;
array[APPEND] = 5;
ALSO:
printNameOf(BookId);
printNameOf(MemberId);
....
define operators on these types (override adding two books,
whatever....) and use them as separate types resting assured there will
be no runtime performance drop!
*Lastly
*(~) isn't very good if calling a function, like printNameOf((~)
(MemberId) 5); will throw an error for the same reason
MemberId x = 5;
printNameOf((~) x);
will throw an error, there is no unique type. No assumptions should be
made here (identity cast, (~) becomes (MemberId), not even that)
That is the hard typedef!
*
*
Notes:
1) Group - in algebra a group is a structure consisting of a set and a
binary operation, let S be a set and @ be a binary operation, a group
has the following properties
* for an x,y in S x@y is also in s (closure) - implied by binary operation
* for an x in S there exists an i (identity element) that's also in S
such that x@i = i@x = x (existence of identity)
* for x,y,z in S x@(y@z) = (x@y)@z (associativity)
* for an x in S there exists a y in S such that x@y = i (the identity
element) (existence of an inverse)
* for an Abelian (excuse my spelling, dyslexic :/) group for an x,y in S
x@y=y@x and is in S also (commutativity)
Quick examples:
Integers (signed :P) under addition, @ = + and S is
{...,-3,-2,-1,0,1,2,3,...}
for integers x,y and z:
x+y is an integer
x+0 = 0+x = x, therefore 0 is the identity and that is an integer too.
x+(y+z) = (x+y)+z, addition is associative
x+(-x) = 0 so -x is the inverse of x, if x is an integer is -x? yes! so
the inverse is in S as required
x+y=y+x (commutative) - note that this would not be the case with
subtraction, x-y != y-x
2x2 matrices of real numbers (floats sort of) under multiplication:
not a group because of the inverse property, if a matrix has a zero
determinant it has no inverse, a requirement. To be a group S would have
to be defined as "4 real numbers such that ad-bc != 0"