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