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 from balsing stuff up, like using a MemberId where you mean BookId and 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"


Reply via email to