> And he said “no” to no one.
And that is the core of the problem.
> And he said “no” to no one.
And that is the core of the problem.
I don't really see this. Most of C++'s complexity emerges naturally from three things:
1. Lack of GC.
2. Direct support for user-defined types on the stack.
3. The desire to implement every feature as efficiently as the corresponding C idiom.
There actually aren't very many controversial features in C++: multiple inheritance, operator overloading, templates, and exception handling are the ones that come to mind. With the exception of multiple inheritance, none of these is particularly heinous. Implementing them under the three restrictions above is where things get complicated.
In most languages my preferred view in the debugger is the source code of the language, in C++ I almost always just looked at the assembly. It seemed the more 'clear' language :)
Lack of GC never bothered me much by the way, that's no different in C, and that's where I really learned how to code, so keeping an eye on memory leaks and the opposite, double frees is somehow a built-in feature (of the programmer, not the language).
C++ is definitely a language where you can get screwed by "clever" programmers who would rather be reading TC++PL than actually coding, but it's not so bad if the people you work with exercise good judgment. I've never had to deal with multiple inheritance in real production code, for instance.
It gives you an excellent overview of the various ways in which things can go wrong, and C++ figured quite prominently in the 'gotcha' department.
C has it's share of issues, double frees, failure to initialize (but most compilers catch that one nowadays), and stale pointers. With a good discipline you can work around those.
C++ can obscure the bugs in such a way that it takes you a long long time before you can 'nail' them. The biggest problem I have with the language is that the code tends to obscure what is going on at the machine level.
I guess that's a 'feature' too, but I prefer to have a more direct correspondence between program code and what goes on below the surface. That's a problem with OO in general by the way, and I think that this is part of what makes software so terribly inefficient these days.
Gigabytes of ram are barely enough to accomodate a single user os, it's really pretty weird.
Which totally defeats the purpose of being backward compatible with C.
If it had been D instead, an actual new version of C designed to incorporate new features, instead of C++, additional features on top of C, it probably would be less reviled but there would have been a lot more resistance to adoption.
In general, there is at least the linearizing (Common lisp) multiple inheritance with a well defined method lookup semantics (even though there are good ways to shoot one into the foot there), and the C++-local-hackity-multiple inheritance (where the programmer has to resolve lookup ambiguities, resulting in unpredictable semantics). And this even avoids the discussions of traits, mixins and such.
Also, note that there is also a semantic difference here between C++ (and Java) and most other languages. In C++ multiple inheritance, if classes A and B define method foo, and A and B have no inheritance relationship, then A::foo and B::foo are entirely unrelated methods -- just as if they were named A::foo and B::bar. The fact that they have the same name does not automatically create a relationship between them, as it would in CLOS or in duck-typed languages like Python. That means that a call to foo in a class that inherits from A and B is just as ambiguous as if the programmer had typed "foo, or maybe bar." This unrelatedness is inherent to C++'s type system. It would make no sense to a C++ programmer to treat A::foo and B::foo as being alternative dispatch targets for the same method call.
Even if you allow the compiler to gloss over this unrelatedness, I still think it is a matter of taste whether it is simpler to require programmer disambiguation or to have a well-defined algorithm for resolving ambiguous names. C++ usually takes heat for providing powerful more-than-meets-the-eye mechanisms that allow programmers to "hide" program semantics inside language features. Here C++ takes the opposite approach and requires the programmer to explicitly resolve ambiguous names, and it takes heat from a CLOS programmer, who could write an entire operating system using a single family of generic functions all having the same name ;-) Damned if you do, damned if you don't.
I sense an upcoming challenge! ;)
Oh, absolutely. I just meant that it adds some incidental complexity to operator overloading that doesn't exist in other languages.
Can you give an example of a "pet construct" (aside from major features like multiple inheritance and exception handling) that could have been left out?
The times that I ran in to examples where it was used properly and elegantly I think it could have been done just as nice with a properly named function call.
I think the way it workd came from the DSL camp arguing that you should be able to make it look as though certain bits where native to the language, even if they weren't, and then the only uses we get are things that have nothing to do with DSLs but everything with children handling power tools (as in: bad idea).
Show me a single example of where operator overloading was necessity to make a function work, and I'll show you a more cleanly coded version using a function call.
The interesting thing is that after the compiler is done with it you'll be calling ::operatorX anyway.
Plenty of times there are naming issues as well, what does it mean to add two items of 'X' together, most times such a statement would be meaningless in the real world, but if you were forced to use a named function instead of an operator that function would have a name that matched what really happens.
Naming stuff is one of the great powers of computer languages, use of operators should be reserved to those situations where the outcome is predictable, and where operator precedence rules are the same as they would be when adding numbers.
So, it's fine to add an instance of a class called 'area' to another instance of that class, but you can't add one instance of 'boat' to another. That one should be done using:
cargospace = add_carcospace(boat1,boat2);
or something to that effect.It's a DSL that is similar to EBNF that generates parsers. It would be considerably more difficult to use without operator overloading.
Unfortunately its use is not quite limited to code like that.
So what's stopping you? That's exactly the way I do it. In my C++ career I've only overloaded math operators for math types, * and -> for smart pointers, [] for container-like types, and () for callable objects. At least in production code... I did some crazy and irresponsible things in college, just like everyone else.
If there's one point I've been wanting to make over and over again in this discussion, it's that misuse of language features is a much smaller problem than people think. (In practice, it is even negligible next to the overhead of reading poor error messages from the STL.) C++ is a very bad programming language for people who fear their coworkers, but so are C, Ruby, and any Lisp with macros. Even Java isn't protection against stupidity: a bad Java programmer on your team can't confuse you, but he can bury you.
Just think of defmacro as implicitly adding an infinite number of infinitely complex language features to Lisp. If you can deal with defmacro in a responsible way, operator overloading shouldn't bother you at all.
Same with language features. If all you have is bicycles then nobody is going to hurt anybody badly. But if we're all driving rocket cars at 1000 MPh then the resulting pile-ups will be spectacular.
You use operator overloading in a disciplined fashion, because you understand fully what the consequences are when you are using them improperly. If a situation is a 'natural' for overloading then you'll use it, otherwise you'll use a function with a descriptive name.
Unfortunately that is not the norm. You'd almost want an '--expert' flag given to the compiler before you are allowed to use those features, and every time you want to use it you get this trick question about some obscure bug that you have to answer :)
The best little C bug I saw in some forum somewhere by the way was this one:
int x,y;
x = 0; /* initialize both x
y = 0; and y to 0 */
domorestuff(x,y);
Of course any C programmer worth his weight will spot that one in a heartbeat, but it is indicative of how easy a language 'feature' (multiline comments) can become a bug.And as the below(above?) poster said - there is syntax highlighting. It's not the greatest example of failure.
So instead of view = projection * model; you would prefer.
view.SetVector(Multiply3x3matrixWith3x1Vector(projection.GetMatrix(),model.GetVector());
Should we allow floats an doubles to be multiplied with a'*' or should that use properly named classes
How does someone looking at the code know which parts of projection and model get multiplied here ?
At least make it:
view.setVector(projection.getMatrix() * model.getVector());
Because '*' applied to a model or a projection could literally mean anything, there might be many elements in those structures that are candidates for multiplication.By making it explicit what gets multiplied the code is less clear than it could be.
If they're simple arrays without further fields attached to them then you could do:
view = matvecmul(projection,model);
And that's pretty close to the overloaded example.Multiplying matrices is no more weird than multiplying a float by an int or a positive and negative number.