Anyone else feel like C++ is getting too complicated?

M

Matthias Buelow

Juha said:
The same source can produce "dynamically" many different types of
compiled machine code, depending on how the template is instantiated.

Then C is also "dynamic", given that:

int i = 0; i++;

can generate a potentially infinite number of different machine code
output, depending on compiler, architecture, and even compiler options.
I thought Lisp has always been nothing but a functional language.

Mainstream dialects have always had assignment and destructive operations.
 
B

bonchbonch


Trolls are often a one-way mouthpiece; they love to rant but hate to
listen. Please, continue whining about other people whining while
your programming language sinks further into obscurity.
 
N

Noah Roberts

Trolls are often a one-way mouthpiece; they love to rant but hate to
listen. Please, continue whining about other people whining while
your programming language sinks further into obscurity.

join him
 
J

Juha Nieminen

Matthias said:
Then C is also "dynamic", given that:

int i = 0; i++;

can generate a potentially infinite number of different machine code
output, depending on compiler, architecture, and even compiler options.

Yes, master nitcpicker.
 
N

Noah Roberts

df said:
Yes, we know what killfiles are, thanks. I was mocking the usage of the
ancient term "plonk" from 1994-era Usenet.

Gad! Troll central in these here parts.
 
T

Tony

Lionel B said:
So you implement your own containers, streams, algorithms, ...
(easy-peasy)
debug them to a high standard of reliability (shouldn't take much work)
and you're fine (except that nobody else understands your code).

"My code" huh? What do you know about "my code"? Hmm? Do tell. Well then
STFU!
 
J

Juha Nieminen

Tony said:
"My code" huh? What do you know about "my code"? Hmm? Do tell. Well then
STFU!

What does he know? You told him: "The solution is to not use the std
library".

If you are indeed not using the std library, I wouldn't really like to
maintain your code.
 
N

Noah Roberts

Juha said:
What does he know? You told him: "The solution is to not use the std
library".

If you are indeed not using the std library, I wouldn't really like to
maintain your code.

Had to work under a guy like this once; no std library, no stl, no
exceptions. Eventually he sort of gave up ever getting anything done
and quit. Best thing that ever happened to the team.
 
I

Ian Collins

Paavo said:
That's precisely what C++ is strong in. In practice, you will encounter
endless unanticipated problems, and the maximum flexibility of C++ actually
helps to overcome them.

In many different ways!
 
T

Tony

Juha Nieminen said:
What does he know? You told him: "The solution is to not use the std
library".

(Aside: I don't mean it when I say "STFU" (it means I was out partying and
am all cocky). I'm in reality, quite mild-mannered).

"Not using the std library" is only a solution if on is able to create or
procure an alternative.
If you are indeed not using the std library, I wouldn't really like to
maintain your code.

I feel the same way about code that uses the std library. So it goes both
ways. "Progress" (a subject term perhaps, so in quotes) in library design
has been hindered by the std library. Don't "get my drift" wrong: I think
C++ is the best thing out there. I just think it is quite far from the
ideal.

Tony
 
T

Tony

Noah Roberts said:
Had to work under a guy like this once; no std library, no stl, no
exceptions. Eventually he sort of gave up ever getting anything done and
quit. Best thing that ever happened to the team.

Sounds like you were an experiment wondering if you could rise to the
challenge.

Tony
 
M

Matthias Buelow

Paavo Helde said:
That's precisely what C++ is strong in. In practice, you will encounter
endless unanticipated problems, and the maximum flexibility of C++ actually
helps to overcome them.

As flexible as a brick that lands on your head, yes.
 
J

Jerry Coffin

[ ... overload resolution: ]
Every C++ programmer *must* know those rules but maybe
1% of them know the WHOLE set of rules because the human
mind is not adapted to absorbing tons of programming trivia.

Quite the opposite: compiler writers need to implement these rules. A
normal programmer only really needs to know one incredibly simple rule:
if it matters in the least which of a set of overloaded functions gets
called under any circumstance, then you shouldn't be using overloading
in the first place.

The whole point of overloading is to provide a set of functions that do
the same thing, but for different input types. As long as they all
really do the same thing, it doesn't matter a bit which one of them gets
selected for any given call.

The only time you care which one is called is if they DON'T do the same
thing -- and in that case, the first step is to decide whether 1) you
have a bug in the set of overloaded functions, or 2) your design was
wrong, and you're using overloading when you really shouldn't have in
the first place.
THEN, it is obviously the task of the maintenance programmer
to debug all those cases where the byzantine rules go
completely in other direction that the poor programmer
intended, when some new code using indirectly those
overloaded functions is written, 2 years AFTER the original
programmer left.

Again, that's simply not true. The maintainence programmer is faced with
the same two possibilities as above: either he doesn't care at all which
of the functions gets used, or he fixed the fact that overloading was
being abused in the first place.

[ ... ]
Behind it is the need to declare a set of types so that
template arguments can be checked by the compiler. OK.

But wasn't *inheritance* supposed to define this stuff?

Yes and no. A class allows you to define a set of characteristics of a
type. Inheritance allows you to state (in essence) that a new type has
(at least) the characteristics of some existing type.

In this case, however, we're interested in allowing usage of ANY type
that has a set of characteristics. In some cases separate types come
about more or less by accident (e.g. different libraries in use). In
other cases we have things that support similar operations, but we still
definitely want them to be different types (e.g. the numeric types in C
or C++).

Now, it's (sort of) true that you can accomplish most of the same things
with inheritance IF you use it pervasively. For example, quite a few
things that will be done with Concepts in C++ could be done with
inheritance in something like Smalltalk, where everything is an instance
of some class. Even there, however, it's difficult to apply well -- in
particular, you frequently end up having to use types much closer to the
root of the whole tree than you'd like. In the process, you end up
allowing a lot of types that you didn't really want to allow.

Looking at it from a slightly different direction, programming languages
are following the same path that databases did decades ago. Databases
progressed from the hierarchical model to the network model and finally
to the relational model. Single inheritance implements the hiearachical
model. Multiple inheritance implements the network model. Concepts
implement the relational model.
 
J

Jerry Coffin

[ ... ]
If I needed some "concept" I would create an abstract class
that has all those properties and the compiler could check
that the given type conforms to all the properties of the
specified class.

Not so. If you use inheritance, you quickly end up with something
virtually indistinguishable from Smalltalk. In particular, your type
checking ends being done at run-time rather than at compile time.

The problem is fairly simple: inheritance means selecting a type far
enough up the tree that it's an ancestor of EVERY type you might care to
work with. Unfortunately, that almost inevitably includes a lot of other
types you don't want to work with.

You also end up really warping the inheritance tree to make this work.

Consider for example, sorting collections of objects. We can compare
strings, so we want to support sorting collections of strings. We can
also compare integers, so we want to support sorting collections of
strings. To support that, we have to apply 'sort' to some common
ancestor of both 'string' and 'integer' -- so far so good.

Now we run into rather a problem though: contrary to your earlier
statement, floating point numbers really canNOT be sorted. In
particular, sorting (at least as normally implemented) requires that if
!(a<b) && !(b<a) then (b==a). A floating point NaN violates that
requirement -- a NaN doesn't compare equal to another NaN, and generally
doesn't even compare equal to itself!

Since we want to allow assignment from integer to floating point, but
not the reverse, floating point should be an ancestor of integer. We've
already seen, however, that string needs to descend from something that
is NOT an ancestor of floating point -- ergo, floating point ends up as
a base of both string and integer. This allows something we really DON'T
want though -- an implicit conversion from any string to floating point.

The alternative is for string and integer to be only distantly related
(sensible) and do the sorting on some abstract type near the root of the
tree that's an ancestor of integer, floating point AND string, and then
check at run time to ensure against attempting to sort things like
floating point numbers that can't be sorted.
The only reason that this is not done is that OO is no longer
"in", i.e. the OO "FAD" has disappeared. We have new fads
now.

Quite the contrary. 30+ years of experience with using inheritance has
shown that while it models some relationships quite well, there are many
other relationships it models quite poorly.

Inheritance requires us to specify similarity in an all or nothing
fashion. Specifically, a derived object shares ALL the characteristics
of its base. If there is too much difference to allow that, then we
can't use inheritance, which prevents us from taking advantage of ANY
similarity.

Concepts allow us to specify the exact degree of similarity between
types necessary for a particular operation. There's no need to create a
new type specifically to encapsulate the similarity needed for this
particular operation, and there's no need to operate on excessivly
abstract objects, and then do run-time checks to ensure that the objects
really do support the operations we care about. Likewise, we don't end
up with unwanted implicit conversions just because SOME operations imply
a relationship that doesn't work otherwise.
 
J

James Kanze

[ ... overload resolution: ]
Every C++ programmer *must* know those rules but maybe
1% of them know the WHOLE set of rules because the human
mind is not adapted to absorbing tons of programming trivia.
Quite the opposite: compiler writers need to implement these
rules. A normal programmer only really needs to know one
incredibly simple rule: if it matters in the least which of a
set of overloaded functions gets called under any
circumstance, then you shouldn't be using overloading in the
first place.

I've argued that a lot myself in the past. But it is worth
pointing out that you don't always have a choice: you can't give
several different names to the constructor, for example. (In my
experience, this doesn't create that many problems in practice,
but it is worth pointing out.) Also, if you get carried away
with overloading (or overload too little), you can get
ambiguities---one helpful rule here is that if the overload set
contains any integral type, it should also contain int.

It's probably also worth pointing out that if you do want a
specific overload, for whatever reason, or if you encounter an
ambiguity, you can always use casts to achieve an exact match.
Even in such cases, you don't really have to understand all of
the details.

And finally, the fact remains that in most reasonable cases,
overload resolution does the right thing; i.e. what you'd
intuitively expect.
 
J

Jerry Coffin

[ ... ]
I've argued that a lot myself in the past. But it is worth
pointing out that you don't always have a choice: you can't give
several different names to the constructor, for example. (In my
experience, this doesn't create that many problems in practice,
but it is worth pointing out.) Also, if you get carried away
with overloading (or overload too little), you can get
ambiguities---one helpful rule here is that if the overload set
contains any integral type, it should also contain int.

It's probably also worth pointing out that if you do want a
specific overload, for whatever reason, or if you encounter an
ambiguity, you can always use casts to achieve an exact match.
Even in such cases, you don't really have to understand all of
the details.

Right -- but these are things to know about overloading, not really
about overload _resolution_. There are definitely some rules of thumb
that are handy to know about overloading -- but you don't really have to
know _any_ of the overload resolution rules to know how to do
overloading perfectly reasonably. In fact, most of the time you can do
overloading perfectly reasonably without knowing many rules at all.

When you get down to it, the only rule I've seen people getting confused
about with respect to actual overload resolution is that when a scope is
found to contain at least one instance of the right name, no further
searching of outer scopes is conducted -- only names in that scope are
considered for overload resolution.

If you want to get really technical, that rule isn't really about
overload resolution per se either. Overload resolution only _starts_
after that search is conducted, and more than one candidate is found.
 
J

James Kanze

[ ... ]
I've argued that a lot myself in the past. But it is worth
pointing out that you don't always have a choice: you can't
give several different names to the constructor, for
example. (In my experience, this doesn't create that many
problems in practice, but it is worth pointing out.) Also,
if you get carried away with overloading (or overload too
little), you can get ambiguities---one helpful rule here is
that if the overload set contains any integral type, it
should also contain int.
It's probably also worth pointing out that if you do want a
specific overload, for whatever reason, or if you encounter
an ambiguity, you can always use casts to achieve an exact
match. Even in such cases, you don't really have to
understand all of the details.
Right -- but these are things to know about overloading, not
really about overload _resolution_. There are definitely some
rules of thumb that are handy to know about overloading -- but
you don't really have to know _any_ of the overload resolution
rules to know how to do overloading perfectly reasonably. In
fact, most of the time you can do overloading perfectly
reasonably without knowing many rules at all.
When you get down to it, the only rule I've seen people
getting confused about with respect to actual overload
resolution is that when a scope is found to contain at least
one instance of the right name, no further searching of outer
scopes is conducted -- only names in that scope are considered
for overload resolution.

The other one that seems to confuse people is that overloading
doesn't come into play until type deduction for template
functions has finished, and the rules concerning implicit
conversions aren't the same for template type deduction.
If you want to get really technical, that rule isn't really
about overload resolution per se either. Overload resolution
only _starts_ after that search is conducted, and more than
one candidate is found.

It's not really a question of overload resolution per se.
Still, they're small problems that do affect actual programmers,
even those writing clean code. In practice, probably not
seriously, but worth mentionning.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top