Somebody explain me this ( I can't find a reason )

Coders forum. Discuss techniques, swap code snippets, ask for help etc. relating to all forms of software development.
User avatar
gilesgoat
Sheepie Whore
Sheepie Whore
Posts: 27258
Joined: Wed Sep 03, 2003 7:54 am 0
Location: Monfalcone, Italy
Contact:

Somebody explain me this ( I can't find a reason )

Post by gilesgoat »

Ok ..

I am modifying shit I haven't written ( and I'll never write it as that ) ..

So .. 2 DAYS OF EXTREME VAFFANCULO on "what the heck is going on here" ..

Original code ...

UsingX = true;

Code: Select all


void Banana(int index)
{
auto &rt = ( UsingX ) ? something[index]->RT0 : othershit.RT1;

// DEBUG
	if (0 == memcmp(&rt, &othershit.RT1, sizeof(rt)))
	{
		PRINTF("MEGAMERD\n");
	}
}

index = 0 .. OK
index = 1 .. OK
index = 2 .. OK
index = 0 .. MEGAMERD ...

WHAT ??

WHAT ?

Then this ..

UsingX = true;

Code: Select all


void Banana(int index)
{
RenderTarget rt = ( UsingX ) ? something[index]->RT0 : othershit.RT1;

// DEBUG
	if (0 == memcmp(&rt, &othershit.RT1, sizeof(rt)))
	{
		PRINTF("MEGAMERD\n");
	}

}

index = 0 .. OK
index = 1 .. OK
index = 2 .. OK
index = 0 .. OK
index = 1 .. OK
index = 2 .. OK
index = 0 .. OK
index = 1 .. OK
index = 2 .. OK

etc. etc. etc. ...

It's compiling using C17++ "rules" ...

WHY that above is happening ? What IS that ??
Giles the billy goat
"Baa"
User avatar
gilesgoat
Sheepie Whore
Sheepie Whore
Posts: 27258
Joined: Wed Sep 03, 2003 7:54 am 0
Location: Monfalcone, Italy
Contact:

Re: Somebody explain me this ( I can't find a reason )

Post by gilesgoat »

Ok I got it ... it wasn't even just that ..

So &rt is literally "almost like saying" <type>* X = &X;

There was ONE line of code in the middle of all things where I was doing a X = something else; intended X to be "something else" but the point is "before that" X was still RT0 so at all effect saying "RT0 = <something else>" ..

.. 2 days of despair for this crap ..
Giles the billy goat
"Baa"
User avatar
Vargol
Baa!
Baa!
Posts: 59
Joined: Wed Jun 05, 2019 7:54 pm 0

Re: Somebody explain me this ( I can't find a reason )

Post by Vargol »

EDIT; BLAH, got ninja'd by Giles himself

Okay, don't know why I'm replying to this as I haven't used C++ since before auto came into the language but I often find getting
an idiot of look at stuff can be useful and send you down paths you didn't think off

I see two potential issues, I'm assuming the issues are 1) the MEGAMERD is the bad result and, 2) the fact that it happens on the second call of Banana(0) but not on the first is even worse.

Is something changing the something array or RT1 between calls ? (I know thats the obvious answer)
e.g. a RenderTarget sounds like something that can have a state or something is changing RT0 to match RT1 or making the reference the same.

UsingX is true, so it should always use RT0, can RT0 be the same as RT1 for rt bytes e.g. if RT0 is XYZ and RT1 is XYZ1 the memcmp would match
Which leads to... is RT1 and RT0 always a RenderTarget, or could they be a Sub/SuperClass with a different size so sizeof(rt) is a wrong comparison size for either RT0 or RT1

The big question in my mind is what does auto do for a item of an array ? Does it use the Array item type i.e. MyClass[] -> MyClass or does it use the type of the item if is a subclass

Is the something array a RenderTarget[] or array based on a super class ?

Depending on what the Class is where the second example will always be using check sizeof (RenderTarget) where as the first example might not.
So my stupid example becomes XYZ* (where * is any old byte) gets compared to XYZ1 and the memcmp fails unless its coincidently 1.
So potentially the failure in the first example you gave is right if the seeming change in value is valid.

Or it might be a compiler bug :-)

Shit code though, comparing values via memcmp just breaks so easily as soon as you move out of the basic types.
User avatar
gilesgoat
Sheepie Whore
Sheepie Whore
Posts: 27258
Joined: Wed Sep 03, 2003 7:54 am 0
Location: Monfalcone, Italy
Contact:

Re: Somebody explain me this ( I can't find a reason )

Post by gilesgoat »

Vargol wrote:EDIT; BLAH, got ninja'd by Giles himself

Okay, don't know why I'm replying to this as I haven't used C++ since before auto came into the language but I often find getting
an idiot of look at stuff can be useful and send you down paths you didn't think off

I see two potential issues, I'm assuming the issues are 1) the MEGAMERD is the bad result and, 2) the fact that it happens on the second call of Banana(0) but not on the first is even worse.

Is something changing the something array or RT1 between calls ? (I know thats the obvious answer)
e.g. a RenderTarget sounds like something that can have a state or something is changing RT0 to match RT1 or making the reference the same.

UsingX is true, so it should always use RT0, can RT0 be the same as RT1 for rt bytes e.g. if RT0 is XYZ and RT1 is XYZ1 the memcmp would match
Which leads to... is RT1 and RT0 always a RenderTarget, or could they be a Sub/SuperClass with a different size so sizeof(rt) is a wrong comparison size for either RT0 or RT1

The big question in my mind is what does auto do for a item of an array ? Does it use the Array item type i.e. MyClass[] -> MyClass or does it use the type of the item if is a subclass

Is the something array a RenderTarget[] or array based on a super class ?

Depending on what the Class is where the second example will always be using check sizeof (RenderTarget) where as the first example might not.
So my stupid example becomes XYZ* (where * is any old byte) gets compared to XYZ1 and the memcmp fails unless its coincidently 1.
So potentially the failure in the first example you gave is right if the seeming change in value is valid.

Or it might be a compiler bug :-)

Shit code though, comparing values via memcmp just breaks so easily as soon as you move out of the basic types.

That "memcmp" I add it as "last resort debug" to see "ARE THOSE THINGS REALLY THE SAME ??" .. and turns out they were ..

Ok I NORMALLY *DO NOT* use "auto" NOR "&" if I want a pointer I explicitely use a pointer.

"something" and "othershit" are both "structures with many different things in them" but they both contain a "Rendertarget" that is another struct with lot of shit in it, however both RT0 and RT1 are the same type.

What I did NOT fully realize ( I don't use much & in that way myself ) is that by passing "&" it's really "at all effects" like if you passed &argument so if auto &rt = a then you have to very careful, of course, that if somewhere else you do rt = something you effectively are going to change whatever "a" is too.

Really "rt = something" is like : Rendertaget *rt = &a; *rt = "something";

I did NOT see there was ONE line of code, amongs many, that was doing much later on an "RT0 = RT1"

Now RT1 in reality changes at each Banana() call, so that "index" so in reality what was happening was :

index = 0 , RT = something[0]->rt0 ...
"At some point" then RT = RT1 !!!!! so at all effects now RT = something[0]->rt0 = RT1[0] !!!!!
The original value of something[0]->rt0 IS LOST !
But for the moment the rest of code still works.

index = 1 , RT = something[1]->rt0 ...
"At some point" then RT = RT1 !!!!! so at all effects now RT = something[1]->rt0 = RT1[1] !!!!!
The original value of something[1]->rt0 IS LOST !
But for the moment the rest of code still works.

index = 2 , RT = something[2]->rt0 ...
"At some point" then RT = RT1 !!!!! so at all effects now RT = something[2]->rt0 = RT1[2] !!!!!
The original value of something[2]->rt0 IS LOST !
But for the moment the rest of code still works.

Fourth loop ..

index = 0 , RT = something[0]->rt0 ...

BUT NOW WAIT ! something[0]->rt0 cause the error of before is NOW RT1[0]

SO I had something after 3 frames that was trying to render into RT0[] .. but "something wasn't going on" for reasons I am not sure but wasn't signalling any error, the result is that frame was left with whatever was already in.

They way I found it was thanks to some "GPU captures" the first thing I saw was like "the GPU says I am using ONLY ONE RT" and that "can't be" I am using TWO not ONE where's one gone ?

Then by looking into the GPU command stream at some point I found "where actually the RT was assigned" and digging down I noted the "dataaddress" i.e. the "actual location of the framebuffer" then looking at my PRINTF() to see if I recognized any number I noted that was identical to some RT in the "swapchain" ..

Then I started to suspect that someway "something was getting a wrong PTR" but I still had NO IDEA why/where/how.

So I started to look/check all memory allocations/etc. I still have no idea how.

Then before to do "any more shit" I grabbed/printed the "DataAddressES" of RT0 and RT1 and put an ( assert dt0 != dt1 ) that failed !

At that point I went for "THIS CANNOT BE, IT LOOKS LIKE RT0 and RT1 are the same" AND THAT CANNOT BE, maybe it just screwed up the dataaddress only ?

Then I did add that memcp() "in despair" and it's like NO, THEY ARE PRECISELY THE SAME THING !

HOW IN THE ()*£*""$

.. then MUCH later on I found that single line ...

If I would have seen "better" that line before it would have saved me 1 day of despair.

Also now took me some hours to remove "all the shit I put in" to debug/try to understand what was going on.

The result was "the gelatinous cube" .. instead of making a rotating cube it was doing the first 3 frames of it that then were stuck in a "perpetual" 0,1,2,0,1,2,0,1,2, loop ...

Oh well .. live and learn ...
Giles the billy goat
"Baa"
User avatar
Dio
Sheepie Whore
Sheepie Whore
Posts: 18887
Joined: Mon May 31, 2004 1:08 pm 0
Location: Newcastle UK

Re: Somebody explain me this ( I can't find a reason )

Post by Dio »

Another thing to be careful of mixing auto and the ? operator is that the two resutls of the ? operator don't have to have the same type, which can cause some... interesting behaviour (especially when the thing before the ? operator is compile-time visible, since the auto thing can be really quite different types).
User avatar
KlaxonCow
Baaaaaaa!
Baaaaaaa!
Posts: 8444
Joined: Fri Dec 13, 2002 3:46 am 0
Location: A large grassy field.

Re: Somebody explain me this ( I can't find a reason )

Post by KlaxonCow »

I never use "auto". Mainly because it contradicts the whole bloody point of having a type system.

The CPU itself has no explicit notion of "data types". RAM is just a really big array of raw bytes, the bits of which can be moved in and out of CPU registers, and instructions manipulate bits in certain ways that assume those bits are a signed integer, a float, a page table record or whatever.

It's all implicit from a consistency of using certain instructions on a certain part of memory that you are treating it like a float or a signed integer or whatever, but nothing is being explicitly recorded nor enforced with regard to that.

It's all on you. The CPU doesn't know and doesn't care.

So high-level languages introduce their notion of "data types", which can be seen as a compiler-provided "usage consistency checker". The high-level language does record - in its own symbol table - what "type" you've declared this section of memory as being (and the syntax of the language itself obliges that declaration, when you create a variable). Then it'll double check for you, as part of the compilation process, that this section of memory is always being used in ways consistent with its "type declaration".

Data type system are there for a reason, and that reason is effectively as a "usage double checker" that's implicitly picking up any inconsistencies of usage that you might otherwise be making.

Data types are NOT there for the CPU - it doesn't really have any notion of them at the silicon level, so it neither knows nor is capable of caring about them - there are there for YOU, the programmer, to double-check and verify your mental logic. The high-level languages adds this extra concept to the party, as a mechanism that's at least, at a primitive level, the beginnings of a "double-check the programmer actually knows what the fuck they are doing" consistency checker (data types alone can't do the whole of that job, but they're the beginnings of such a thing).

Indeed, data type systems in high-level languages are really part of the class of "static analysis tools" - the most primitive beginnings of such a thing - alongside "lint" and others.

Thus, the very notion of trying to by-pass or avoid or get around a data type system is contradictory to its entire raison d'etre.

Personally, I want the data type system to be there. Indeed, where there is a "strict mode", I always turn it on.

Because whenever I have my thoughts in a straight line and I'm thinking consistently about the problem correctly, the data type system never gets in my way - besides the exercise of, yes, having to type out data type names - but that I'd say is the actual "double check" in a nutshell, that the compiler is always double-checking that I'm saying it's the same thing throughout, and if what I say in a particular place differs from what's recorded elsewhere, I'm being inconsistent... and it's almost certainly caught a "thinking error" on my part.

I feel that if a type system is really such a pain for a programmer, they're not thinking consistently enough. When it's all straight in your head, then type systems don't get in the way and they don't, in fact, do anything.

So if you want to avoid or by-pass the type system and it all feels like a terrible burden on your shoulders then you need to stop and think some more. Get things straight in your head. This is a symptom of you not understanding the problem / your code well enough.

And, to be fair, as a younger programmer, I did initially go through that phase of rather hating the data type system because it was this pain that was getting in my way... but that's because I was doing shit wrong, and I wasn't well enough mentally trained to think in the right ways.

It soon passed and, thereafter, I have no interest in ever avoiding or by-passing it. Indeed, as I say, if there is a "strict mode" I turn it on. I often get annoyed that the data type systems of some languages really aren't strict enough. Too permissive, too ready to automatically convert between types. I wish that "typedef" wasn't just an alias, but actually genuinely created a new user type that's fundamentally incompatible, so that I could more easily create different types of "int"s and "float"s that you aren't permitted to mix and match, without explicit casting.

Well, there are ways via creating a class wrapper to do something like this - and if it's really important in the code, as a place where I could likely fall down and mustn't, maybe I'll actually bother - but that's all a bit too complicated and convoluted for ordinary everyday usage. Something built into the language that does it all for you would be ideal, then I'd happily "typedef" a whole array of incompatible user types to make things even stricter than they already are.

And, indeed, we can see that OOP is kind of like an elaborate extension to the data type system, where you actually are creating your own brand new data types. Classes are data types, and they are, by definition, incompatible with every other data type, unless you explicitly say otherwise, or dance the dance of a "type cast" to use it in some "out of type" manner.

Like, I find myself not remotely wanting to by-pass or avoid data types. I'm actually annoyed that they typically aren't strict enough. That even "strict mode" really isn't challenging my thinking often enough to catch my failures of understanding. That C++, though introducing OOP, also is backwards compatible with C underneath, so it does still have some auto-converts on primitive data types.

So I feel I have no use for "auto". Other than maybe it's there to save me on typing.

But, hey, you know me, verbosity doesn't remotely bother me. On the contrary, verbosity is a "double check". When I write my "long posts", I'm totally exposing my reasoning - my "workings out" - to the world, and - if I've made a mistake - someone out there can totally point to exactly where my train of thought went wrong. Because I'm laying it all out, so you can exactly see that that's where I made a logical leap too far. Or that's where I believed something that, actually, I can show is not actually true.

In the "short term vs. long term" view, saving me a fraction of a second here and there on the keyboard is absolutely NOT worth having to nit-pick out all of tiny little "thinking mistakes" from a massive code base weeks and months later.

It's actually, in the overall, a big false economy to do that. You want to catch failures as immediately as possible, because the sooner you do it, the cheaper it is.

To the logical - though unachievable - extreme that, you know, if you never once coded a single bug in the first place, then your "debugging effort" is literally nothing whatsoever. You won't be able to do that, of course - "to err is to be human" - but that's the "cheapest" ideal to be striving for. Don't make any mistakes in the first place, then the cost of fixing them later is literally nothing. And, in that regard, I consider the data type systems I'm using to be a friend, not a foe.

I want them there to be straightening out my "thinking errors" at the earliest possible opportunity - including the notion that simply because the data type system exists, it's forcing me to think in those terms and ensure my thinking is straight in that regard, even before I've typed a single key on my keyboard. That its mere existence is helping to force out those "thinking errors" as early as you can possibly get - as its existence means I have to think in those terms and, because I am, I catch my own mistakes before I've even made them. And that's the ideal.

So I want it to be there, and I don't want to be able to by-pass or avoid it.

(Except, you know, here and there rarely, as I'm quite literally wanting to grab that "float" as if it's an "int" to do some kind of bit manipulation on it or something - when I am knowingly defying it with good purpose and reason, but that is a rarity and probably only shows up when doing "extreme optimisation", which Knuth reminds us is probably the last thing I should be doing. And I mean quite literally the last thing, as you should always optimise "top down" - algorithmic optimisations are the faster and bigger wins - and so if I'm at the level of messing with CPU instructions and bit manipulation, it logically should be quite literally "the last thing I should be doing". I should only ever be there, if I've genuinely exhausted every other optimisation above it. So if I'm ever knee-deep in CPU-level optimisation stuff then I should only be there if I've genuinely exhausted all higher level algorithmic optimisations first. And, in truth, at least logically, that shouldn't be possible and I should never find myself doing that. But, ah, humans are limited and fallible - I might have exhausted everything I can possibly think of. But that clearly just tells me that I haven't thought of everything.)

I mean, I'm not religious and it's not "taboo" or anything, so if "auto" is the best thing to do at a particular point, then I'll do it. But, generally, as it is kind of contradictory to the whole point of the exercise, I tend to never find it's ever logically the best thing to do at any point.

Kind of like "goto". Ordinarily, I'll never use it. Because things should all be neatly structured and broken down into sensible units, and I just find I have zero reason to ever deploy it. But if "goto" is the solution - like, say, needing to break out of many nested loops or error handling - then I'll happily use it. I use to code BASIC, "goto" and line numbers are a thing I can do. I just shouldn't be doing it, when there are better options. And there should, generally speaking, always be better options.


p.s. it's like when they proposed having ID cards in the UK. Advocates would simultaneously say that "ah, they won't get in your way" yet were also saying that ID cards would somehow infallibly catch all terrorists and paedos and such with perfection.

Sorry, you can't logically have both. If the ID card is always being checked, then that's inconvenient - but it's catching those criminals. If it's never being checked, then that's more convenient - but then the criminal's ID is never been checked either, so it's not catching them out.

You can't have both. Either ID cards are constantly checked - and we're catching all those criminals - or they're never checked - which is convenient, but also means that the ID cards actually do nothing whatsoever in helping to catch any criminals... or we have some trade-off in-between those two extremes, to check often enough to be useful, but not too often to piss everyone off.

But the point is you can't have both extremes at the same time. Yes, a constant "ID check" would be a pain in the arse, but that's how the cards are catching criminals.

There's the same kind of underlying logic with type checking systems and bugs, really. One or the other, but you can't have both simultaneously.
"Words ought to be a little wild - for they are assaults of thought on the unthinking."
- John Maynard Keynes
User avatar
gilesgoat
Sheepie Whore
Sheepie Whore
Posts: 27258
Joined: Wed Sep 03, 2003 7:54 am 0
Location: Monfalcone, Italy
Contact:

Re: Somebody explain me this ( I can't find a reason )

Post by gilesgoat »

@KlaxonCow : TL TL TL ..

1. This code I am studying/modifying is "definitely not mine" it's the only tutorial/basic sample I have "simpler than the others" that I am studying/working out to make "something I need/can use" from it

2. It has a "coding style" that is a bit "full of all those C++ fancy things" and "takes in" a ton of things from "a framework" that is pretty big and I spent quite some time to "extrapolate/take out/re-do what I need cause I can't take the whole thing in".

3. They crammed "all that they could cram in this single thing" to show how to do this & that most of which I don't need at the moment so I spent quite some time "slimming it out" also understand what all that stuff really does.

4. The code is really complex, despite "the general sense of what it does you can grasp it soon" it's ALL THE DETAILS about "how is really done" that things get quite complex

5. This code IMHO should be made, and it's what I am doing for MY own purpose/way of use (*) , into a static lib "with a few key functions" to link with / use and it's what I am just doing, instead it's a "sample/tutorial" that shows you how many things can be done ( in various ways ) and lets all up to you to "cook up something from it".

Imagine you have a car and "all you'd like to have" are some functions like :

GetTheCarReadyToStart()
OpenTheDoor()
CloseTheDoor()
GetIn()
StartEngine()
DriveTheCar()
StopEngine()
GetOut()
OpenTheDoor()
CloseTheDoor()
PutAwayTheCar()

Or let's say at least that's what *I* want for what *I* need, I put emphasis on "I" because (*) that could be NOT what YOU need.

Then imagine I give you "a sample code" that shows you "all the car can do", "20 variants of possible doors and ways to open it","how to get 4 different coats of paint","how to re-configure the dashboard/controls/wheels/ways the door open" "and we don't even talk about the engine and its possibilities" and "accessories" ..

And you say "fine fine but I need ONE or TWO different types of cars for what I need NOT ALL the possible cars".

But then "the code that makes the car" you find that "the door alone" needs "the hinges library/framework", "the keyholes framework" "the door handles framework", "the door paint framework" "the opening door action framework" , etc. etc. .. and those too have "variants" depending if you want to drive that car "faster" or "more secure" or "more sport" or "more family way" ..

And then again "that shows you everything about the car", but it doesn't really show you "how to put it on the road and make it drive".

For that there's another "uber complex tutorial/sample" that "has to build some road for you to drive" and we get back to "more frameworks" for the road, the obstacles, the thing and again it's "a simplified/closed circuit run" not "a real life run".

So from all that info "that I never seen before" in about 2 weeks time I have to make :

- a car that starts runs, drives and stops and doesn't look/performs too crap

- a car able to function in the roads/circuits *I* already made where I have other older cars running

- a car that doesn't take/need "tons of foreing stuff" to work

- a car that *I* must be able to fix/modify if it breaks/does not run as we like

- a car that at least to some point I know pretty well how it works and what it can do

And finally "possibly find some new tricks this car can do".

So that's more or less the version of the story, I am getting there but as you can see "it can be a bit confusing" ..
Giles the billy goat
"Baa"
User avatar
Dio
Sheepie Whore
Sheepie Whore
Posts: 18887
Joined: Mon May 31, 2004 1:08 pm 0
Location: Newcastle UK

Re: Somebody explain me this ( I can't find a reason )

Post by Dio »

I share the general reservation that auto can make code harder to read (because you have to go look up what the type is) and readability is the most important thing about code.

But there are cases when it's essential FOR readability: nobody needs std::vector<std::pair<int, std::string>>::const_iterator in their life, while auto i = tuples.cbegin() is easily read.
User avatar
gilesgoat
Sheepie Whore
Sheepie Whore
Posts: 27258
Joined: Wed Sep 03, 2003 7:54 am 0
Location: Monfalcone, Italy
Contact:

Re: Somebody explain me this ( I can't find a reason )

Post by gilesgoat »

Dio wrote:I share the general reservation that auto can make code harder to read (because you have to go look up what the type is) and readability is the most important thing about code.

But there are cases when it's essential FOR readability: nobody needs std::vector<std::pair<int, std::string>>::const_iterator in their life, while auto i = tuples.cbegin() is easily read.
To be fair I prefer a lot "strong typed imperative languages" I think my favourite ( albeit quite verbose ) so far is VHDL where "everything has to be precisely decleared and there's very little room for mistakes" I like those languages where "if it compiles it should run unless you make some really logical mistakes".

C/C++ unfortunately "it's all the opposite of that" "if it compiles does not mean anything".

VHDL anyway is very verbose it wants "everything to be specified to a T" but given its purpose makes perfectly sense.
Giles the billy goat
"Baa"
User avatar
KlaxonCow
Baaaaaaa!
Baaaaaaa!
Posts: 8444
Joined: Fri Dec 13, 2002 3:46 am 0
Location: A large grassy field.

Re: Somebody explain me this ( I can't find a reason )

Post by KlaxonCow »

Dio wrote:But there are cases when it's essential FOR readability: nobody needs std::vector<std::pair<int, std::string>>::const_iterator in their life, while auto i = tuples.cbegin() is easily read.
Agreed. But, of course, there are other ways to skin a cat.

So, you could have "typedef std::pair<int, std::string> StudentIDName;" and then it's just "std:vector<StudentIDName>".

Which isn't just saving on the typing, but you're actually giving some context as well. So this is also telling someone who's reading this code that what we've really got here is a vector of Student ID and names.

Like, the "typedef" isn't just saving on typing out the long thing, it's an opportunity to put context and semantics in there. That all this relates to student records, something you couldn't immediately derive from the data types alone.

(As I touched on, the one thing I'd like is if "typedef" really created a genuine user-defined data type, so that some other "std::pair<int, std::string>" was actually NOT compatible with "StudentIDName", unless you specifically said so with a cast or something.

I mean, yes, in C++, you can effectively do that by creating a full-blown class / struct out of it. But that's too much time / effort / complexity for what, really, ought to just be a built-in feature of the language, then and there, with one simple "typedef".

Particularly because, yes, the language is already doing that for structs and classes - as a "struct cow { int x; int y };" is NOT compatible with a "struct fish { int x; int y };" out of the box and the name matters - so this is not asking for something that the compiler can't do. It already does it, but could we also have it do the same thing with a "typedef"? So it's not just an alias for the long thing, but actually a genuine user-defined data type.

This is really what, underlying it all, you're doing in OOP itself. Creating a bunch of user-defined data types - classes - and it'd be nice if you could do that, with a one-liner "typedef", for simpler primitive data types too. So that I can "typedef int StudentID;" and that it's not just an alias for "int", but it's actually it's own new type. So that "void function(StudentID id)" would actually have to be given a parameter of "StudentID" type, and you can't just bung it any old "int".

So you could actually logically separate out all the different types of "int" or "unsigned int" or "float" you've got in your code, and also - at the same time - give them more meaningful readable names. That "StudentID" is a student's ID code - which happens to be just an "int" under the hood, sure, but that's an "implementation detail", right? - and then you can specifically say that this function takes a "StudentID" and that's properly enforced.

But, ah, I do, of course, know why. Because C didn't do that. And so C++'s backwards compatibility guarantee obliges it to also do that. That said, even C doesn't see two otherwise equivalent structs with different names as being the same type. The mechanism is, frustratingly, already there in the compiler. But it annoyingly makes exemption for the primitive data types. I reckon it really shouldn't have.

A "typedef" should, as its very name impliies, should genuinely create a user-defined data type - which then enjoys that same struct-like / class-like incompatibility with every other type, regardless of whether its internal definition is or isn't the same as something else - but they fucked up. It's just an alias. So it's only a "save typing" mechanism, when it totally could have been so much more, hooking into the data type system properly.)

And, indeed, in an OOP way, then seeing "StudentID" in the code makes it an opaque type. I mean, if you look up the "typedef" you can see it's just an "int" - nothing fancy going on - but that's an "implementation detail". I mean, if the format of student ID codes changed, then maybe you shouldn't know that it's really an "int" under the hood, so you don't depend on that fact - you know, per standard OOP principles.

Basically, I feel that this is C "leaking" into C++, which should have taken the more strict version of "typedef" I suggest. In order to be more correctly OOP.

But, also, this sort of thing would improve readability. Because now you're not looking at "std::pair<int, std::string>" and thinking "what the hell is that?" but you see "StudentIDName" and you have context and meaning as to what the hell's going on (but you can look up its "typedef" and see that it's just an "int" under the hood, if that "implementation detail" is at all relevant).

Something along those lines is really the route things should be going down - working WITH the typing system to make it more readable and stricter, rather than trying to work AGAINST it and undermine it.

I just feel that "auto" is the wrong solution to that problem. Don't undermine the type system, but actually make greater use of it.

Code: Select all

typedef int StudentID;
typedef std::string StudentName;
typedef std::pair<StudentID, StudentName> StudentIDName;
typedef std::vector<StudentIDName> NewStudentsList;
And this isn't just saving on typing, but it's actually giving meaning and context to what these "int"s and "std::string"s actually relate to. It makes it more readable and understandable.

And if only "typedef" genuinely created a new user-defined type, this could also be catching errors too.

E.g.

Code: Select all

typedef int StudentID;
typedef int Count;

Count NumStudents = 5;
StudentID studentID = NumStudents
Would actually trip up the type system. Sure, they're both "int"s, but they have different semantics and it's NOT permissible to assign a count to an ID number.

Plus, this way of doing things would be DRY too.

In that, if you wanted to change "StudentID" from an "int" to a "long" - because you've had a big influx of 4 billion students that a 32-bit "int" ain't cutting it anymore or whatever (okay, contrived example, as that's never really going to happen) - then you change it in one place only. Then recompile. Done.


p.s. there is a hack where you wrap a primitive data type like "int" or "float" inside a struct - because structs and classes are inherently incompatible with each other, regardless of what's inside them - to fake a genuine "typedef". But it is a hack. It is a bit ugly. It really should just be what "typedef" does out of the box. Because, not least, that's totally what the word "typedef" is implying. It's saying "type definition" - so it really should genuinely define a type, as its name is very strongly implying it should.

p.p.s. I'm not religious about this. If code has "auto" then I'll work with it. I can put up with this, but I do still think, on a philosophical level, it's the wrong solution to the problem. Sometimes, "auto" could, indeed, be handy and the right way to go - seeing as things are as they are. But I still think things shouldn't be like that, this isn't the right approach and undermining the type system is, in the end, a bad idea.

I'm with VHDL. Remove all ambiguity. Specify EVERYTHING to a T.

It's only a route to inviting programmer error, and there are better ways to "save on typing" that can actually tighten things up, rather than loosening them to the point of accidentally letting in dumb careless errors everywhere.
"Words ought to be a little wild - for they are assaults of thought on the unthinking."
- John Maynard Keynes
User avatar
Dio
Sheepie Whore
Sheepie Whore
Posts: 18887
Joined: Mon May 31, 2004 1:08 pm 0
Location: Newcastle UK

Re: Somebody explain me this ( I can't find a reason )

Post by Dio »

Naah, I disagree. If the type is clear from the expression then auto is fine and more readable than the typedef.

(Maybe because typedef is one of those things I also have a bit of a leery philosophical relationship with. It feels like the wrong solution to the problem - it does nothing but add a new name for something and if that's hiding a lot of complexity... in some ways it's not dissimilar to auto in its opaque-ness).

Let's remember that clearer and simpler code is in itself a route - an utterly critical one - to avoiding programmer error. If the code is readable it is much more likely to be correct. It's not about saving on typing. It's about code readability.
User avatar
KlaxonCow
Baaaaaaa!
Baaaaaaa!
Posts: 8444
Joined: Fri Dec 13, 2002 3:46 am 0
Location: A large grassy field.

Re: Somebody explain me this ( I can't find a reason )

Post by KlaxonCow »

Dio wrote:If the type is clear from the expression then auto is fine and more readable than the typedef.
To quote the Spartans: "If".

IF the type is clear from the expression. Maybe. Maybe not. It depends.

The "typedef" route, though, is always consistently the same every time.


You yourself gave the example of "std::vector<std::pair<int, std::string>>::const_iterator". That might show up and "auto" might help there.

But the "typedef" route - as I showed - can be used to break that down into parts, give meaningful semantic type names - "StudentID", "StudentName", "StudentIDName" - too each of those parts. Aliases that can be used throughout the code, so you're implicitly labelling that this "int" here is a "StudentID" and that this other "int" over there is a "Count" of students or whatever.

Note that despite my lament that "typedef" doesn't really do what its name says it does - genuinely creating a new user-defined type - the above still remains true and works with the "alias" version of typedef that C / C++ has. It's just a missed opportunity, when you work this way, to not have the compiler also help you catch those semantical errors too, when it totally has the information, at that point, to do so (because it's already doing it for structs and classes).

Another consistency is that, in C++, this is what you're doing anyway. When you create a class, you're defining a user-defined data type (and a genuine one, not just an alias, which is why it's annoying "typedef" doesn't do the same, as the mechanism is already within the compiler - classes and structs do behave this way).

Like, your mileage may vary and you might prefer something else, but this isn't some random thing I've made up for the shits and giggles, but it does all plug in and remains consistent with the whole OOP viewpoint. Which if you're already following that in C++, this is just a further extension of those concepts.
Dio wrote:It feels like the wrong solution to the problem - it does nothing but add a new name for something
As I've just covered, I fully agree with the aliasing thing. It should go further than that and do as its own bloody name suggests, and genuinely defines a new data type.

A new data type that the compiler can put into its memory and then double-check for you, in the exact same way that it already does that for structs and classes (which are also user-defined data types, of course).
Dio wrote:and if that's hiding a lot of complexity... in some ways it's not dissimilar to auto in its opaque-ness).
Yes, but it's purposeful opaqueness. It's OOP "information hiding" - hiding the implementation details that you don't need to know and avoids you wrongly depending on things you shouldn't.

But, you know, OOP "information hiding" is not true opaqueness, in that you can always look it up in the source code (kind of like "private" and "public" - you can still see the "private" stuff in the source code, but you're controlling the access to ensure that folks aren't using / depending on "implementation details" that they shouldn't be, as you might well be changing those in future).
Dio wrote:Let's remember that clearer and simpler code is in itself a route - an utterly critical one - to avoiding programmer error. If the code is readable it is much more likely to be correct. It's not about saving on typing. It's about code readability.
And what's more readable?

Code: Select all

std::vector<std::pair<int, std::string>> studentList;
Or, once we've established a few "typedefs" up front:

Code: Select all

typedef int StudentID;
typedef std::string StudentName;
typedef std::pair<StudentID, StudentName> StudentIDName;
typedef std::vector<StudentIDName> StudentList;
Then we have:

Code: Select all

StudentList studentList;
And you access the "StudentList" with a "StudentID", in order to map to the "StudentName".

The type names are exactly saying what they semantically are - a student ID, a student name, a list of students, etc. - so, you know, how is that not "readable"?

Moreover, in my "typedefs" above, you'll notice that I'm breaking down the complexity of "std::vector<std::pair<int, std::string>>" into its component parts, and then labelling each of those parts.

And you know the saying: "How do you eat an elephant?". One bite at a time. The way you tackle complexity is to break it down into smaller less-and-less complex parts.

And you can then, of course, totally use "StudentID" elsewhere in the code. So you could have a function / method like "StudentName &getName(StudentID id)". Again, it's totally spelling out what the function semantically does, where "std::string &getName(int id)" really doesn't (and, indeed, you might then be tempted to name that function "std::string &getStudentName(int student_id)" or something to stuff the semantics back in there and self-document the code, but the type names I made earlier do that already).

You do have to set up the "typedefs" ahead of time, of course. But, thereafter, you can get much more readable code everywhere else. As "StudentID" is exactly the same as "int" - because it's just an alias under C / C++ - but it exactly says what it is. Spells it out. It's a student ID number. Their database record number, most probably.

And, if you like, then you can even "Hungarian Notation" this as well. So it's "iStudentID", where "i" is "int". And you could have "f" for "float" and so forth. I'm not a fan of Hungarian Notation, but if you deployed that, then you could totally pack all necessary information into the type's name itself. Then it's not just semantic, it's also no longer opaque.


p.s. I'm not saying that anyone else has to do this, if they don't want. I'm just defending the fact that I'm not mad to suggest this route, as being entirely valid and that it has a bunch of advantages to it.

(And if only "typedef" actually did what its name suggests, we'd even get proper compiler errors, helping to catching our semantic mistakes too. But C / C++ missed that golden opportunity - even though, as I say, the very name "typedef" says that that's what it should be doing. "typedef" = "type definition". Okay, so actually do that. Don't just create an alias, actually define a user-defined type!!)
"Words ought to be a little wild - for they are assaults of thought on the unthinking."
- John Maynard Keynes
User avatar
Dio
Sheepie Whore
Sheepie Whore
Posts: 18887
Joined: Mon May 31, 2004 1:08 pm 0
Location: Newcastle UK

Re: Somebody explain me this ( I can't find a reason )

Post by Dio »

auto i = list.cbegin(); is much more readable than your typedefs above, rather than having to look up which typedef applies.

Hungarian notation, and most of its derivatives, have been shown to be provably worse for readability in studies - there's not space to carry enough information about the type, and it's a reasonable goal that the declaration of the type should be visible or at most one page away from most uses of the type, excepting members. (Sticking a p in front of pointers is the one exception).
User avatar
KlaxonCow
Baaaaaaa!
Baaaaaaa!
Posts: 8444
Joined: Fri Dec 13, 2002 3:46 am 0
Location: A large grassy field.

Re: Somebody explain me this ( I can't find a reason )

Post by KlaxonCow »

Dio wrote:auto i = list.cbegin(); is much more readable than your typedefs above, rather than having to look up which typedef applies.
Is it?

The actual equivalent to "auto i = list.cbegin();", using my typedefs, would be "StudentList::const_interator i = list.cbegin();".

So my version is literally spelling out that it's an iterator over a "StudentList". It's a little longer - although part of that is my deliberate decision not to include "const_iterator" inside any typedefs - but it's self-documenting in-place.

And I guess it depends on what IDE you're using. But, in Eclipse, for example, hovering the mouse over "StudentList" would actually show its declaration right there in a tooltip, or selecting "Open Declaration" - which is F3 on the keyboard - can jump you to the declaration.

As I said, I'm not saying you have to do this. If you don't like it, then that's fine.

But stop with the implications, as if it's not a valid alternative approach or that I'm totally mad to suggest it or something.


And a big point of comparison here is that that "auto" covers only that single line of code. Whereas, my "typedefs" are covering the entire program.

Sure, you look up what "StudentID" is - but then it applies to the entire code, everywhere. Whereas, the "auto" there only covers that one line, and if you did need to know what it was, then you've got to go look up what the "cbegin()" method returns and so forth (which isn't, in practice, any less effort than looking up my "typedefs" - except my typedefs apply throughout the code, looking up what that "auto" actually equates to - from about the same amount of effort to look it up - only helps you in that one spot and nowhere else).

Single examples don't show that difference. "auto" is only helping you in that singular line of code. The "typedefs" are decorating the entire program.

You don't like it? Fine. But it does work, and there are - as I covered - legitimate reasons to use it.
"Words ought to be a little wild - for they are assaults of thought on the unthinking."
- John Maynard Keynes
User avatar
zip accident
Sheepie Whore
Sheepie Whore
Posts: 13928
Joined: Sun Feb 20, 2005 1:58 pm 0
Location: Bristol, UK

Re: Somebody explain me this ( I can't find a reason )

Post by zip accident »

KlaxonCow wrote:
Dio wrote:auto i = list.cbegin(); is much more readable than your typedefs above, rather than having to look up which typedef applies.
Is it?

The actual equivalent to "auto i = list.cbegin();", using my typedefs, would be "StudentList::const_interator i = list.cbegin();".

So my version is literally spelling out that it's an iterator over a "StudentList". It's a little longer - although part of that is my deliberate decision not to include "const_iterator" inside any typedefs - but it's self-documenting in-place.

And I guess it depends on what IDE you're using. But, in Eclipse, for example, hovering the mouse over "StudentList" would actually show its declaration right there in a tooltip, or selecting "Open Declaration" - which is F3 on the keyboard - can jump you to the declaration.

As I said, I'm not saying you have to do this. If you don't like it, then that's fine.

But stop with the implications, as if it's not a valid alternative approach or that I'm totally mad to suggest it or something.


And a big point of comparison here is that that "auto" covers only that single line of code. Whereas, my "typedefs" are covering the entire program.

Sure, you look up what "StudentID" is - but then it applies to the entire code, everywhere. Whereas, the "auto" there only covers that one line, and if you did need to know what it was, then you've got to go look up what the "cbegin()" method returns and so forth (which isn't, in practice, any less effort than looking up my "typedefs" - except my typedefs apply throughout the code, looking up what that "auto" actually equates to - from about the same amount of effort to look it up - only helps you in that one spot and nowhere else).

Single examples don't show that difference. "auto" is only helping you in that singular line of code. The "typedefs" are decorating the entire program.

You don't like it? Fine. But it does work, and there are - as I covered - legitimate reasons to use it.
So, a return to COBOL?
"My way is the way of not fighting":- the book of Enter the Dragon.
Post Reply