Email conversation between myself and Marshall Cline

Some background: Marshall Cline has served on the ISO C++ and Smalltalk standardisation committees. He maintains the C++ FAQ Lite which is how we ended up conversing.

In the following conversation which required myself to sit down for at least four hours per night for a week, I learned a lot of stuff and possibly Marshall may even have learned one or two things as well. In the following X emails, we cover the following subjects:

You can return to nedprod.com here


From: Niall Douglas <[email protected]>
To: [email protected]
Subject: Comments on your C++ FAQ
Date: Sat, 27 Jul 2002 00:05:45 +0200

Firstly, it looks good, and I learned some things (like delete NULL; 
isn't supposed to cause an exception). Thanks.

However ...

I would prefer if you said making classes final leafs is evil. The 
use of private constructors with static ctors should be evil. 
*Protected* constructors with the same is fine. The reason I say this 
is recently I've had to modify quite a lot of code which kept its 
internal data and constructors private and hence my subclasses 
couldn't access them. My view is that good C++ should never ever 
think it won't be subclassed by someone without the sources - to that 
end I make most private variables protected and I rely on discipline 
not to access them. The same should go for constructors. I of course 
fully support commenting it with "subclass this under threat of death 
signed JS". (I know one solution is for the third party to modify the 
header files but that seems dirty and error prone to me).

Regarding static initialisation problems, for objects the trick is to 
create a helper class which initialises the static objects on 
construction and destructs them appropriately. The static members 
should be pointers or references to the classes. You can either new 
the pointers or else contain the classes in your helper class and 
write the pointers or references in the constructor. Then you simply 
instantiate the helper class first thing in main() and then it 
destructs last thing when main() exits.

Templates are funny things on different platforms. I ran into a 
problem which compiled fine on Linux but MSVC6 fell over where I was 
doing this:
int compareItems(void *a, void *b)
return ((T *) a)<((T *) b);

MSVC complained there was no < operator for T during the template 
definition, not at actual template use to create a class (where I 
think it should throw an error if the T passed doesn't have the 
operator). To get around it, I created a dummy class with the 
operator which I made all T's subclasses thereof and now MSVC shut 
up. Annoying though. My point is, I'd prefer your section on 
templates to point out more the stuff which doesn't work as it should 
- you make it sound like it all works according to spec, which it 
rarely in my experience does.

Lastly, I know you think macros are evil, but I have always used them 
to implement functionality which C++ should have. The biggest of 
these is try...finally without which multithreaded programming is a 
severe pain. Of course, you don't call the macro 'finally' - I've 
used TERRH_TRYF...TERRH_FINALLY...TERRH_ENDTRYF to make it very 
clear. In this context, macros are very useful and I think to be 
encouraged so long as the macro: (a) says where it's defined and (b) 
all caps.

Anyway, hope you don't mind these comments. I would hardly consider 
myself much good at C++, I've only been programming in it for three 
years or so and it still befuddles me at times (unlike in C or 
assembler, where I always know what to do).

Cheers,
Niall Douglas




From: "Marshall Cline" <[email protected]>
To: "'Niall Douglas'" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Fri, 26 Jul 2002 20:01:03 -0500

Hi Niall,

Thanks for your thoughtful note. Unfortunately I disagree with most
of your suggestions (see below for details). But I don't way my
disagreements to detract from my appreciation that you took the time
to write in the first place.

[See details below.]

Niall Douglas wrote:
>I would prefer if you said making classes final leafs is evil. The
>use of private constructors with static ctors should be evil.
>*Protected* constructors with the same is fine. The reason I say
>this is recently I've had to modify quite a lot of code which kept
>its internal data and constructors private and hence my subclasses
>couldn't access them. My view is that good C++ should never ever
>think it won't be subclassed by someone without the sources

Sorry, you're wrong above: good C++ classes very well might (and
often do!) want to make sure they don't get subclassed. There are
lots of reasons for this, including the practical reality that the
constraints on a base class is much more stringent than a leaf
class, and in some cases those constraints make it very expensive,
and perhaps even impossible, to make a class inheritable. When
people inherit anyway, all sorts of bad things happen, such as the
slicing problem (AKA chopped copies), the covariant parameter on the
assignment operator, etc., etc.

So making a class a leaf / final is *not* evil. In some cases *not*
making a class into a leaf / final is evil! In other words, in
cases like these, the programmer is not simply *allowed* to make the
class final, but has an actual fiduciary responsibility to do so.
It would be professionally irresponsible to *not* make the class a
leaf / final.

I recognize that you've had a bad experience trying to inherit from
a class with private stuff. But you must not generalize that bad
experience or take it to mean leaf classes are universally bad.
Your bad experience may mean the base class's programmer did
something wrong, or it may mean you did something wrong. But the
guideline / rule you propose ("final / leaf classes are evil") will
do more harm than good.

>- to
>that end I make most private variables protected and I rely on
>discipline not to access them. The same should go for
>constructors. I of course fully support commenting it with "subclass
>this under threat of death signed JS". (I know one solution is for
>the third party to modify the header files but that seems dirty and
>error prone to me).
>
>Regarding static initialisation problems, for objects the trick is
>to create a helper class which initialises the static objects on
>construction and destructs them appropriately. The static members
>should be pointers or references to the classes. You can either new
>the pointers or else contain the classes in your helper class and
>write the pointers or references in the constructor. Then you simply
>instantiate the helper class first thing in main() and then it
>destructs last thing when main() exits.

Sorry, but this is a very poor solution. It certainly works in a
few cases, but it violates the first premise of pluggable software.
The three solutions described in the FAQ are much better: they
handle all the trivial cases that yours handles, and in addition
they handle the more sophisticated "plugability" cases.


>Templates are funny things on different platforms. I ran into a
>problem which compiled fine on Linux but MSVC6 fell over where I was
>doing this:
>
>int compareItems(void *a, void *b)
>return ((T *) a)<((T *) b);
>
>MSVC complained there was no < operator for T during the template
>definition, not at actual template use to create a class (where I
>think it should throw an error if the T passed doesn't have the
>operator).

There must be something wrong with your code or your description,
because the code you've given above will never generate an error
irrespective of the type 'T'. The '<' is applied between two
pointers, not between two 'T's.

(I'm obviously assuming you fix the compile-time bugs in the code,
e.g., by adding 'template<class T>', and by wrapping the function
body in '{' and '}'.)


>To get around it, I created a dummy class with the
>operator which I made all T's subclasses thereof and now MSVC shut
>up. Annoying though. My point is, I'd prefer your section on
>templates to point out more the stuff which doesn't work as it
>should - you make it sound like it all works according to spec,
>which it rarely in my experience does.

I am interested in this error, but I'm only interested in it if it
is real, that is, if you can get MS VC++ to actually generate an
incorrect error message. I'd suggest rewriting the example, trying
it, and seeing if you can actually create a case that fails with MS
VC++.


>Lastly, I know you think macros are evil, but I have always used
>them to implement functionality which C++ should have. The biggest
>of these is try...finally without which multithreaded programming is
>a severe pain. Of course, you don't call the macro 'finally' - I've
>used TERRH_TRYF...TERRH_FINALLY...TERRH_ENDTRYF to make it very
>clear. In this context, macros are very useful and I think to be
>encouraged so long as the macro: (a) says where it's defined and (b)
>all caps.

Sorry, but I would strongly recommend against the above. That's
just not the C++ way of doing things. To be honest, it makes you
sound like you're a Java programmer who has learned C++ syntax but
still hasn't mastered C++ idioms.


>Anyway, hope you don't mind these comments.

Not at all.

Marshall




From: Niall Douglas <[email protected]>
To: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Sun, 28 Jul 2002 03:42:12 +0200

On 26 Jul 2002 at 20:01, Marshall Cline wrote:

> Sorry, you're wrong above: good C++ classes very well might (and
> often do!) want to make sure they don't get subclassed. There are
> lots of reasons for this, including the practical reality that the
> constraints on a base class is much more stringent than a leaf class,
> and in some cases those constraints make it very expensive, and
> perhaps even impossible, to make a class inheritable. 

I had thought one was meant to encourage reusability in every line of 
C++ you wrote? Where possible obviously.

To that end, and I will admit I have only worked on small C++ 
projects (less than 5k lines), it has always appeared to me not 
hugely difficult to structure your code appropriately to ensure 
maximum reusability.

> When people
> inherit anyway, all sorts of bad things happen, such as the slicing
> problem (AKA chopped copies), the covariant parameter on the
> assignment operator, etc., etc.

Surely chopped copies are an optimisation problem for the compiler? 
You can help it of course by trying to ensure subclasses never undo 
or redo operations the base class did.

I'm afraid I don't understand covariant parameter on the assignment 
operator.

If what you say about inheriting is bad, then I would say my code is 
very foul. I have huge depths of subclassing as a normal part of 
writing my code. It's not just me - I picked up the idea from Qt 
(http://www.trolltech.com/). I've basically learned C++ by copying 
their example.

> I recognize that you've had a bad experience trying to inherit from a
> class with private stuff. But you must not generalize that bad
> experience or take it to mean leaf classes are universally bad. Your
> bad experience may mean the base class's programmer did something
> wrong, or it may mean you did something wrong. But the guideline /
> rule you propose ("final / leaf classes are evil") will do more harm
> than good.

No all he/she did wrong was assume no one would ever want to subclass 
their class. Not only is that arrogant and assumes themselves 
infallible, it seems to me bad practice and against the whole idea of 
software reusability.

> >Regarding static initialisation problems, for objects the trick is to
> >create a helper class which initialises the static objects on
> >construction and destructs them appropriately. The static members
> >should be pointers or references to the classes. You can either new
> >the pointers or else contain the classes in your helper class and
> >write the pointers or references in the constructor. Then you simply
> >instantiate the helper class first thing in main() and then it
> >destructs last thing when main() exits.
> 
> Sorry, but this is a very poor solution. It certainly works in a few
> cases, but it violates the first premise of pluggable software. The
> three solutions described in the FAQ are much better: they handle all
> the trivial cases that yours handles, and in addition they handle the
> more sophisticated "plugability" cases.

I'll review your FAQ's suggestions again. Maybe I missed something, 
but they all seemed to have fairly substantial caveats.

> There must be something wrong with your code or your description,
> because the code you've given above will never generate an error
> irrespective of the type 'T'. The '<' is applied between two
> pointers, not between two 'T's.

Agreed. Here is the actual code:
template<class type> class TEXPORT_TCOMMON TSortedList : public 
QList<type>
{
...
virtual int compareItems( QCollection::Item s1, QCollection::Item s2 
)
{
if(*((type *) s1)==*((type *) s2)) return 0;
return (*((type *) s1)<*((type *) s2) ? -1 : 1 );
}
}

This code generates an error saying operators == and < for class type 
don't exist. Changing type to T or anything else doesn't help. Oddly, 
almost identical code compiles elsewhere plus it worked on Linux last 
time I compiled it (quite some time ago).

That's MSVC 6 SP6.

> I am interested in this error, but I'm only interested in it if it is
> real, that is, if you can get MS VC++ to actually generate an
> incorrect error message. I'd suggest rewriting the example, trying
> it, and seeing if you can actually create a case that fails with MS
> VC++.

Hardly important unless Visual Studio .NET has the same problem. MS 
no longer consider MSVC6 a primary support product.

I have tried changing it around, but in the end I have bigger 
priorties. In the end, if it causes me too many problems, I'll switch 
to GCC.

> >Lastly, I know you think macros are evil, but I have always used them
> >to implement functionality which C++ should have. The biggest of
> >these is try...finally without which multithreaded programming is a
> >severe pain. Of course, you don't call the macro 'finally' - I've
> >used TERRH_TRYF...TERRH_FINALLY...TERRH_ENDTRYF to make it very
> >clear. In this context, macros are very useful and I think to be
> >encouraged so long as the macro: (a) says where it's defined and (b)
> >all caps.
> 
> Sorry, but I would strongly recommend against the above. That's
> just not the C++ way of doing things. To be honest, it makes you
> sound like you're a Java programmer who has learned C++ syntax but
> still hasn't mastered C++ idioms.

God no, I hated Java. I'm actually an assembler programmer originally 
which of course uses loads of macros all the time.

How, might I ask, would you suggest you implement try...finally 
without macros in "the C++ way of doing things"? I am assuming you 
will surely agree multithreaded programming is not fun without 
try...finally (if you don't, I want reasons, it'll be interesting to 
see what you'd come up with).

> >Anyway, hope you don't mind these comments.
> 
> Not at all.

I used to read the reports of the ANSI committee meetings regarding 
C++ as it was still being formalised and it always struck me as being 
an awful hodge-podge. The more I learn about it, the more I realise I 
was correct! I never had much experience with it, but Objective C 
always seemed a lot cleaner. Unfortunately, today is now and the 
world we live in uses C++.

I take it you come from a Smalltalk background? I know this will 
sound approaching blasphemous - and I don't mean at all to be 
offensive, but merely to garner an opinion - but I have always 
considered OO to be a good way of organising maintainable source but 
really crap for designing code. I suppose I still write C++ like I 
did assembler (and thereafter C) in that fashion, and hence our great 
difference in style.

Cheers,
Niall




From: "Marshall Cline" <[email protected]>
To: "'Niall Douglas'" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Sat, 27 Jul 2002 20:26:46 -0500

Niall Douglas wrote:
>On 26 Jul 2002 at 20:01, Marshall Cline wrote:
>
>> Sorry, you're wrong above: good C++ classes very well might (and
often 
>> do!) want to make sure they don't get subclassed. There are lots of 
>> reasons for this, including the practical reality that the
constraints 
>> on a base class is much more stringent than a leaf class, and in some

>> cases those constraints make it very expensive, and perhaps even 
>> impossible, to make a class inheritable.
>
>I had thought one was meant to encourage reusability in every line of 
>C++ you wrote? Where possible obviously.

Nope. In fact, that approach normally causes projects to fail.

Instead of encouraging reusability in every line of C++ code (where
possible), the goal is to be a responsible professional who sometimes
invests effort in a future pay-back (AKA reuse), and sometimes does not.
A responsible professional does not invest in a future pay-back when the
ROI isn't right (return-on-investment) or when doing so would add
unacceptable risk or cost or time to the current project. Balancing the
future with the present is a very subtle task, but that is the task of a
responsible professional.


>To that end, and I will admit I have only worked on small C++ 
>projects (less than 5k lines), it has always appeared to me not 
>hugely difficult to structure your code appropriately to ensure 
>maximum reusability.

Everyone, including you, is to some extent a prisoner of their past.
Your experience with very small projects limits your ability to
understand how things work in the real world with large projects. I
don't fault you for your lack of experience with large projects, but in
a similar way you must not presume that your small-system experiences
are applicable or relevant to the way things happen in that other world.


>> When people
>> inherit anyway, all sorts of bad things happen, such as the slicing 
>> problem (AKA chopped copies), the covariant parameter on the 
>> assignment operator, etc., etc.
>
>Surely chopped copies are an optimisation problem for the compiler? 

No, not at all. They are logical errors - places where the compiler is
*required* by the language to generate code that does "the wrong thing."
So even if the compiler had a *perfect* optimizer, it would still be
required by the language generate code that the programmer would
ultimately consider a bug. But it's not a bug in the compiler; it's a
bug in the programmer's code.


>You can help it of course by trying to ensure subclasses never undo 
>or redo operations the base class did.

No, not at all.

I don't think you understand what the slicing problem (AKA chopped
copies) is all about. I suggest you read the FAQ on that one.


>I'm afraid I don't understand covariant parameter on the assignment 
>operator.

I don't think the FAQ covers this one. I'll try to give a very brief
overview. Suppose someone inherits D from B, and passes a D to a
function expecting a B& (reference-to-B). If that function assigns to
the B&, then only the B part of the D object is changed, and the object
often goes into a nonsensical state. It's kind of like genetic
engineering, where biologists scoop out the chromosomes from one animal,
and replace them with the chromosomes of another. It's real easy to get
garbage when you do that.


>If what you say about inheriting is bad, then I would say my code is 
>very foul. I have huge depths of subclassing as a normal part of 
>writing my code. 

Ouch, that's certainly a very dubious design style. It's a typical
hacker's style, and it comes from the Smalltalk world, but it's
generally inappropriate for C++ or Java or any other statically typed OO
language.


>It's not just me - I picked up the idea from Qt 
>(http://www.trolltech.com/). I've basically learned C++ by copying 
>their example.

If you're building an application framework (especially if you're
building a GUI framework), you might have chosen a reasonable piece of
software to learn from. If you're simply *using* a framework to build
some other app, you've chosen poorly.


>> I recognize that you've had a bad experience trying to inherit from a

>> class with private stuff. But you must not generalize that bad 
>> experience or take it to mean leaf classes are universally bad. Your 
>> bad experience may mean the base class's programmer did something 
>> wrong, or it may mean you did something wrong. But the guideline / 
>> rule you propose ("final / leaf classes are evil") will do more harm 
>> than good.
>
>No all he/she did wrong was assume no one would ever want to subclass 
>their class. Not only is that arrogant and assumes themselves 
>infallible, it seems to me bad practice and against the whole idea of 
>software reusability.

You seem to have a wrong notion of how reusability and inheritance are
supposed to mix. Inheritance is not "for" reuse. One does *not*
inherit from something to reuse that thing.


>> >Regarding static initialisation problems, for objects the trick is
to 
>> >create a helper class which initialises the static objects on 
>> >construction and destructs them appropriately. The static members 
>> >should be pointers or references to the classes. You can either new 
>> >the pointers or else contain the classes in your helper class and 
>> >write the pointers or references in the constructor. Then you simply
>> >instantiate the helper class first thing in main() and then it 
>> >destructs last thing when main() exits.
>> 
>> Sorry, but this is a very poor solution. It certainly works in a few
>> cases, but it violates the first premise of pluggable software. The 
>> three solutions described in the FAQ are much better: they handle all
>> the trivial cases that yours handles, and in addition they handle the
>> more sophisticated "plugability" cases.
>
>I'll review your FAQ's suggestions again. Maybe I missed something, 
>but they all seemed to have fairly substantial caveats.

They all do. But at least they all solve the pluggability problem,
which is typically the core reason for using this sort of syntax /
technique.


>> There must be something wrong with your code or your description, 
>> because the code you've given above will never generate an error 
>> irrespective of the type 'T'. The '<' is applied between two 
>> pointers, not between two 'T's.
>
>Agreed. Here is the actual code:
>template<class type> class TEXPORT_TCOMMON TSortedList : public 
>QList<type>
>{
>...
> virtual int compareItems( QCollection::Item s1,
QCollection::Item s2 
>)
> {
> if(*((type *) s1)==*((type *) s2)) return 0;
> return (*((type *) s1)<*((type *) s2) ? -1 : 1 );
> }
>}

This is an interesting example. Please do three things:

1. Send me the code for template class QList (Qt has a template called
QValueList, but I didn't find one called QList).
2. What is the type of QCollection::Item?
3. What is the '___' in TSortedList<____> that caused this error? Or
are you saying it always generates an error independent of any
TSortedList<___> usage?? If the latter, better send me the whole
TSortedList template.


>This code generates an error saying operators == and < for class type 
>don't exist. Changing type to T or anything else doesn't help. Oddly, 
>almost identical code compiles elsewhere plus it worked on Linux last 
>time I compiled it (quite some time ago).
>
>That's MSVC 6 SP6.
>
>> I am interested in this error, but I'm only interested in it if it is
>> real, that is, if you can get MS VC++ to actually generate an 
>> incorrect error message. I'd suggest rewriting the example, trying 
>> it, and seeing if you can actually create a case that fails with MS
>> VC++.
>
>Hardly important unless Visual Studio .NET has the same problem. MS 
>no longer consider MSVC6 a primary support product.

Not true. Many companies will continue to use MS VC++ 6 for years to
come. I know a company that's still using MS VC++ version 1.62 for some
of their embedded systems programming.


>I have tried changing it around, but in the end I have bigger 
>priorties. In the end, if it causes me too many problems, I'll switch 
>to GCC.
>
>> >Lastly, I know you think macros are evil, but I have always used
them 
>> >to implement functionality which C++ should have. The biggest of 
>> >these is try...finally without which multithreaded programming is a 
>> >severe pain. Of course, you don't call the macro 'finally' - I've 
>> >used TERRH_TRYF...TERRH_FINALLY...TERRH_ENDTRYF to make it very 
>> >clear. In this context, macros are very useful and I think to be 
>> >encouraged so long as the macro: (a) says where it's defined and (b)
>> >all caps.
>> 
>> Sorry, but I would strongly recommend against the above. That's just
>> not the C++ way of doing things. To be honest, it makes you sound 
>> like you're a Java programmer who has learned C++ syntax but still 
>> hasn't mastered C++ idioms.
>
>God no, I hated Java. I'm actually an assembler programmer originally 
>which of course uses loads of macros all the time.
>
>How, might I ask, would you suggest you implement try...finally 
>without macros in "the C++ way of doing things"? 

Use the C++ idiom that "destruction is resource reclamation."


>I am assuming you 
>will surely agree multithreaded programming is not fun without 
>try...finally (if you don't, I want reasons, it'll be interesting to 
>see what you'd come up with).

Constructor/destructor <==> resource acquisition/reclamation.


>> >Anyway, hope you don't mind these comments.
>> 
>> Not at all.
>
>I used to read the reports of the ANSI committee meetings regarding 
>C++ as it was still being formalised and it always struck me as being
>an awful hodge-podge. The more I learn about it, the more I realise I 
>was correct!

Have you ever been on *any* ANSI or ISO standardization committee? If
not, it must be easy for you to sit there with zero experience and throw
insults at the hard work of others who have selflessly sacrificed their
time and money to do something big.


>I never had much experience with it, but Objective C 
>always seemed a lot cleaner.

If ObjC is so much better, why is it so unpopular?


>Unfortunately, today is now and the 
>world we live in uses C++.
>
>I take it you come from a Smalltalk background? 

Not at all. My C++ "smells like" C++, not like Smalltalk or assembler
or anything else. Similarly my C code smells like C code, and it uses C
idioms, etc., and my Java smells like Java, etc. I am language neutral,
e.g., I've been a member of both the ANSI C++ and ANSI Smalltalk
committees.


>I know this will 
>sound approaching blasphemous - and I don't mean at all to be 
>offensive, but merely to garner an opinion - but I have always 
>considered OO to be a good way of organising maintainable source but 
>really crap for designing code.

Another really big error. OO is primarily a design approach. The
concept of "OO programming" is very close to a misnomer, since OO
programming cannot stand on its own - it needs OO *design*.

Marshall




From: Niall Douglas <[email protected]>
To: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Mon, 29 Jul 2002 15:06:30 +0200

On 27 Jul 2002 at 20:26, Marshall Cline wrote:

> >I had thought one was meant to encourage reusability in every line of
> > C++ you wrote? Where possible obviously.
> 
> Nope. In fact, that approach normally causes projects to fail.
> 
> Instead of encouraging reusability in every line of C++ code (where
> possible), the goal is to be a responsible professional who sometimes
> invests effort in a future pay-back (AKA reuse), and sometimes does
> not. A responsible professional does not invest in a future pay-back
> when the ROI isn't right (return-on-investment) or when doing so would
> add unacceptable risk or cost or time to the current project. 
> Balancing the future with the present is a very subtle task, but that
> is the task of a responsible professional.

I think we're actually agreeing here, but it's a case of opinion 
dictating emphasis. I tend to always overengineer because I believe 
15% extra development time halves your debugging time and quarters 
augmentation time and many projects suffer from fuzzy definition, so 
this approach makes sense (and I have used it successfully many 
times).

However, I also completely agree with your statement.

> >To that end, and I will admit I have only worked on small C++ 
> >projects (less than 5k lines), it has always appeared to me not
> >hugely difficult to structure your code appropriately to ensure
> >maximum reusability.
> 
> Everyone, including you, is to some extent a prisoner of their past.
> Your experience with very small projects limits your ability to
> understand how things work in the real world with large projects. I
> don't fault you for your lack of experience with large projects, but
> in a similar way you must not presume that your small-system
> experiences are applicable or relevant to the way things happen in
> that other world.

Remember I have worked on >30k C projects and a number of obscenely 
large all-assembler projects and I've been doing all this 
successfully for a decade now. Now I don't claim for a moment to know 
much about C++, but in the end code is code and while organising C++ 
like assembler might not be a good idea it's still better than no 
organisation at all. Hence, by analogy, I believe my considerable 
prior experience does give me an advantage overall, although I will 
freely admit some things will have to be unlearned for C++. 
Unfortunately, that is something that only comes with experience and 
time - although talking with people like yourself, examining existing 
code and reading resources like your C++ FAQ accelerate the process.

> >You can help it of course by trying to ensure subclasses never undo
> >or redo operations the base class did.
> 
> No, not at all.
> 
> I don't think you understand what the slicing problem (AKA chopped
> copies) is all about. I suggest you read the FAQ on that one.

No I didn't, but I do now. It's strongly related to below.

> >I'm afraid I don't understand covariant parameter on the assignment
> >operator.
> 
> I don't think the FAQ covers this one. I'll try to give a very brief
> overview. Suppose someone inherits D from B, and passes a D to a
> function expecting a B& (reference-to-B). If that function assigns to
> the B&, then only the B part of the D object is changed, and the
> object often goes into a nonsensical state. It's kind of like genetic
> engineering, where biologists scoop out the chromosomes from one
> animal, and replace them with the chromosomes of another. It's real
> easy to get garbage when you do that.

I was under the /strong/ impression references were treated 
syntaxically identical to their non-referenced version. Hence, you 
can't pass anything other than B to a function expecting a B&. I 
distinctly remember twigging I should no longer use C style pointers 
except when I explicitly expect D* and all ptrs to subclasses thereof 
(and where I think in the future I may pass a subclass).

> >If what you say about inheriting is bad, then I would say my code is
> >very foul. I have huge depths of subclassing as a normal part of
> >writing my code. 
> 
> Ouch, that's certainly a very dubious design style. It's a typical
> hacker's style, and it comes from the Smalltalk world, but it's
> generally inappropriate for C++ or Java or any other statically typed
> OO language.

Can you point me to resources explaining why this is bad and not just 
a question of individual style? I would have thought it /better/ for 
statically typed languages because the compiler is given more 
knowledge with which to optimise.

> >It's not just me - I picked up the idea from Qt 
> >(http://www.trolltech.com/). I've basically learned C++ by copying
> >their example.
> 
> If you're building an application framework (especially if you're
> building a GUI framework), you might have chosen a reasonable piece of
> software to learn from. If you're simply *using* a framework to build
> some other app, you've chosen poorly.

I'd like to think I accumulate ideas from whatever existing source I 
look at. It is after all how I originally taught myself how to 
program. Again, I'd like to know precisely why this style would be a 
poor choice for some other app.

> >No all he/she did wrong was assume no one would ever want to subclass
> > their class. Not only is that arrogant and assumes themselves
> >infallible, it seems to me bad practice and against the whole idea of
> > software reusability.
> 
> You seem to have a wrong notion of how reusability and inheritance are
> supposed to mix. Inheritance is not "for" reuse. One does *not*
> inherit from something to reuse that thing.

I really don't get you here. I reread the appropriate sections in 
your FAQ and I *still* don't get this. I can't escape thinking that 
what I and you mean by "reusing code" is not the same thing - for me, 
whenever you inherit something you inherit its structure (API) and 
its code - that, to me, is reusing already written and tested code 
which is a good thing. Hence inheritance = code reuse.

> >> Sorry, but this is a very poor solution. It certainly works in a
> >> few
> >> cases, but it violates the first premise of pluggable software. The
> >> three solutions described in the FAQ are much better: they handle
> >> all
> >> the trivial cases that yours handles, and in addition they handle
> >> the
> >> more sophisticated "plugability" cases.
> >
> >I'll review your FAQ's suggestions again. Maybe I missed something,
> >but they all seemed to have fairly substantial caveats.
> 
> They all do. But at least they all solve the pluggability problem,
> which is typically the core reason for using this sort of syntax /
> technique.

Pluggability = ability to link in or out a "module" of code easily 
yes?

If so, static class constructs worry me because I can't guarantee 
their order before main(). My approach solves this, and hence answers 
the main caveat your FAQ stated.

> This is an interesting example. Please do three things:
> 
> 1. Send me the code for template class QList (Qt has a template called
> QValueList, but I didn't find one called QList).

Yeah Trolltech renamed QList to QPtrList in Qt 3.0.

> 2. What is the type
> of QCollection::Item?

MSVC thinks it's a void *. QGList I included should say more on this.

> 3. What is the '___' in TSortedList<____> that
> caused this error? Or are you saying it always generates an error
> independent of any TSortedList<___> usage?? If the latter, better
> send me the whole TSortedList template.

It's the latter. I've commented out the original code in what I've 
sent to get it to compile. If you compare it to QSortedList.h, the 
two are almost identical (which is intentional) but QSortedList.h 
compiles perfectly whereas mine stops complaining with:

> d:\atoms\tclient\include\tsortedlist.h(57) : error C2678: binary '=='
> : no operator defined which takes a left-hand operand of type 'class
> type' (or there is no acceptable conversion)
> d:\atoms\tclient\include\tsortedlist.h(56) : while compiling
> class-template member function 'int __thiscall
> TSortedList<class type>::compareItems(void *,void *)'
> d:\atoms\tclient\include\tsortedlist.h(58) : error C2678: binary '<' :
> no operator defined which takes a left-hand operand of type 'class
> type' (or there is no acceptable conversion)
> d:\atoms\tclient\include\tsortedlist.h(56) : while compiling
> class-template member function 'int __thiscall
> TSortedList<class type>::compareItems(void *,void *)'

This is at template definition, not at template use.

> >Hardly important unless Visual Studio .NET has the same problem. MS
> >no longer consider MSVC6 a primary support product.
> 
> Not true. Many companies will continue to use MS VC++ 6 for years to
> come. I know a company that's still using MS VC++ version 1.62 for
> some of their embedded systems programming.

Companies may continue to use a product, but it's not in Microsoft's 
commercial interests to encourage them. Based on historical 
precident, it is extremely clear Microsoft take a bug much more 
seriously if it's in the current top-of-the-line product. Bugs in 
older products are more likely to be fixed if (a) new product's fix 
can be retro-engineered easily and (b) if their reputation would 
suffer if they didn't. I'm guessing (a) won't apply given the likely 
substantial redesign to accommodate C#.

> >How, might I ask, would you suggest you implement try...finally
> >without macros in "the C++ way of doing things"? 
> 
> Use the C++ idiom that "destruction is resource reclamation."
> 
> >I am assuming you 
> >will surely agree multithreaded programming is not fun without 
> >try...finally (if you don't, I want reasons, it'll be interesting to
> >see what you'd come up with).
> 
> Constructor/destructor <==> resource acquisition/reclamation.

That is a cracking idea I am kicking myself for not having thought of 
earlier. I was already concerned about the overhead of throwing an 
exception every try...finally, but your approach is far simpler and 
more efficient. It'll require quite a lot of code refitting, but I 
think it's worth it.

Thank you!

> >I used to read the reports of the ANSI committee meetings regarding
> >C++ as it was still being formalised and it always struck me as being
> >an awful hodge-podge. The more I learn about it, the more I realise I
> > was correct!
> 
> Have you ever been on *any* ANSI or ISO standardization committee? If
> not, it must be easy for you to sit there with zero experience and
> throw insults at the hard work of others who have selflessly
> sacrificed their time and money to do something big.

I apologise if you interpreted my words as throwing insults for they 
were not intended as such. I have the utmost respect and admiration 
for any standardisation committee (with possible exception of the 
POSIX threads committee, their poor design really screws C++ stack 
unwinding which is unforgiveable given how recently it was designed).

However, this does not changed my statement that C++ is an awful 
hodge-podge. I am not saying everyone involved in standardisation 
didn't move heaven and earth to make things as good as they could, 
but with an albatross like keeping existing code compatibility with 
AT&T C++ and C there was only so much that could be done. I remember 
the passionate debates about what compromises to strike well.

Put it this way: when you try something which seems logical in C it 
generally works the way you think it should. The same in C++ is much 
less true - I keep finding myself running into limitations which have 
no good reason. For example, the concept of destination type seems to 
have no effect in C++ eg;

TQString foo;
foo="Hello world";

Now TQString is a subclass of QString, and both have const char * 
ctors. The compiler will refuse to compile the above code because 
there are two methods of resolving it. Now, to me, that seems stupid 
because quite clearly the destination type is TQString and the 
shortest route to that is to use the TQString const char * ctor ie; I 
clearly am inferring to use the shortest route. The same sort of 
thing applies to overloading functions - you cannot overload based on 
return type, something I find particularly annoying.

> >I never had much experience with it, but Objective C 
> >always seemed a lot cleaner.
> 
> If ObjC is so much better, why is it so unpopular?

Lots of reasons. If I remember correctly, there were many problems 
with the run-time library on different platforms. There were issues 
regarding Next and Apple and all that. Of course, as well, there were 
culture issues - programmer inclinations. Also, there was good 
competition between many C++ vendors which brought C++ tools to a 
decent quality pretty quickly.

Computer history is strewn with cases of an inferior product 
destroying a superior product. It's hardly unique.

> >I take it you come from a Smalltalk background? 
> 
> Not at all. My C++ "smells like" C++, not like Smalltalk or assembler
> or anything else. Similarly my C code smells like C code, and it uses
> C idioms, etc., and my Java smells like Java, etc. I am language
> neutral, e.g., I've been a member of both the ANSI C++ and ANSI
> Smalltalk committees.

In which case you are a better programmer than I. I essentially 
program the same in any language using an internal methodology and my 
measure of my liking a language is how little it distorts what I 
actually want to do (hence my strong dislike of Java and 
VisualBasic). Nothing I program is what other people call a typical 
style of that language. You may think that irresponsible and arrogant 
of me, but I know it is an innate quality of mine - it's the same 
when I learn human languages (I still retain my own speech formation 
and pronounciation irrespective).

Hence, I am more of a functional programmer than anything else. It is 
my dream to some day design an imperitive/functional hybrid language 
which would perfectly reflect how I like to program.

> >I know this will 
> >sound approaching blasphemous - and I don't mean at all to be 
> >offensive, but merely to garner an opinion - but I have always 
> >considered OO to be a good way of organising maintainable source but
> >really crap for designing code.
> 
> Another really big error. OO is primarily a design approach. The
> concept of "OO programming" is very close to a misnomer, since OO
> programming cannot stand on its own - it needs OO *design*.

No, I must disagree with you there: design is independent of 
language. I have never agreed with OO design as my university 
lecturers found out - I quite simply think it's wrong. Computers 
don't work naturally with objects - it's an ill-fit.

What computers do do is work with data. If you base your design 
entirely around data, you produce far superior programs. Now I will 
agree OO is good for organising source for improved maintainability, 
but as a design approach I think it lacking.

An example: take your typical novice with OO. Tell them the rules and 
look at what they design. Invariably, pure OO as designed against the 
rules is as efficient as a one legged dog. In fact, in my opinion, OO 
experience is actually learning when to break pure OO and experienced 
OO advocates do not realise that they so automatically break the pure 
application of what they advocate.

A practical example: at university, we had to design a program to 
sort post office regional codes. The typical class effort, for which 
they received top marks, sorted the list in about ten to twenty 
seconds. My effort did it so quickly there wasn't a delay in the 
command prompt returing - and may I add, I received a bare pass mark 
because I adopted a data-centric solution and not an OO one. Now I 
couldn't fault that (the story of my entire degree), but it painfully 
reminded me of how OO is fundamentally incorrect for computers - good 
for humans, but not computers.

Anyway, I've enclosed the files you requested plus others I thought 
would aid you. TSortedList.cpp should almost stand alone against Qt 
3.0 - at least, anything not defined should have an obvious 
equivalent.

Cheers,
Niall




From: "Marshall Cline" <[email protected]>
To: "'Niall Douglas'" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Sun, 28 Jul 2002 22:31:36 -0500

Niall Douglas wrote:
>On 27 Jul 2002 at 20:26, Marshall Cline wrote:
>
>>>I had thought one was meant to encourage reusability in every line of
>>> C++ you wrote? Where possible obviously.
>>
>>Nope. In fact, that approach normally causes projects to fail.
>>
>>Instead of encouraging reusability in every line of C++ code (where 
>>possible), the goal is to be a responsible professional who sometimes 
>>invests effort in a future pay-back (AKA reuse), and sometimes does 
>>not. A responsible professional does not invest in a future pay-back 
>>when the ROI isn't right (return-on-investment) or when doing so would
>>add unacceptable risk or cost or time to the current project. 
>>Balancing the future with the present is a very subtle task, but that 
>>is the task of a responsible professional.
>
>I think we're actually agreeing here, but it's a case of opinion 
>dictating emphasis. I tend to always overengineer because I believe 
>15% extra development time halves your debugging time and quarters 
>augmentation time and many projects suffer from fuzzy definition, so 
>this approach makes sense (and I have used it successfully many 
>times).
>
>However, I also completely agree with your statement.

Sounds like the difference may be one of degrees, as you said.

I spoke last time about being a prisoner of our pasts. My past includes
acting as "senior technology consultant" to IBM throughout North
America, which meant advising on product strategy, mentoring, and (most
relevant to this situation) performing internal audits. The audits
included a number of important engagements with IBM's clients, and
required me to perform assessments of people and technology. During
these audits and assessments, I saw a lot of large projects that failed
because of overengineering. Many of the technologists on these sick or
dead projects had a similar perspective to what you articulated above.
Their basic approach was often that overengineering is better than
underengineering, that it's cheaper in the long run, and perhaps cheaper
in the short run, so let's overengineer just in case.

As a result of seeing in excess of one hundred million dollars worth of
effort (and numerous careers) washed down the drain, I tend to make sure
there is a realistic ROI before adding any effort that has a
future-payback.


>>>To that end, and I will admit I have only worked on small C++
>>>projects (less than 5k lines), it has always appeared to me not
>>>hugely difficult to structure your code appropriately to ensure
>>>maximum reusability.
>>
>>Everyone, including you, is to some extent a prisoner of their past. 
>>Your experience with very small projects limits your ability to 
>>understand how things work in the real world with large projects. I 
>>don't fault you for your lack of experience with large projects, but 
>>in a similar way you must not presume that your small-system 
>>experiences are applicable or relevant to the way things happen in 
>>that other world.
>
>Remember I have worked on >30k C projects and a number of obscenely 
>large all-assembler projects and I've been doing all this 
>successfully for a decade now. 

Okay, I didn't realize that earlier. That makes some sense now.


>Now I don't claim for a moment to know 
>much about C++, but in the end code is code and while organising C++ 
>like assembler might not be a good idea it's still better than no 
>organisation at all. Hence, by analogy, I believe my considerable 
>prior experience does give me an advantage overall, 

Certainly true.

>although I will 
>freely admit some things will have to be unlearned for C++. 

Also true.

>Unfortunately, that is something that only comes with experience and 
>time - although talking with people like yourself, examining existing 
>code and reading resources like your C++ FAQ accelerate the process.
>
>>>You can help it of course by trying to ensure subclasses never undo 
>>>or redo operations the base class did.
>>
>>No, not at all.
>>
>>I don't think you understand what the slicing problem (AKA chopped
>>copies) is all about. I suggest you read the FAQ on that one.
>
>No I didn't, but I do now. It's strongly related to below.

Agreed.


>>>I'm afraid I don't understand covariant parameter on the assignment 
>>>operator.
>>
>>I don't think the FAQ covers this one. I'll try to give a very brief 
>>overview. Suppose someone inherits D from B, and passes a D to a 
>>function expecting a B& (reference-to-B). If that function assigns to
>>the B&, then only the B part of the D object is changed, and the 
>>object often goes into a nonsensical state. It's kind of like genetic
>>engineering, where biologists scoop out the chromosomes from one 
>>animal, and replace them with the chromosomes of another. It's real 
>>easy to get garbage when you do that.
>
>I was under the /strong/ impression references were treated 
>syntaxically identical to their non-referenced version. Hence, you 
>can't pass anything other than B to a function expecting a B&. I 
>distinctly remember twigging I should no longer use C style pointers 
>except when I explicitly expect D* and all ptrs to subclasses thereof 
>(and where I think in the future I may pass a subclass).

Hopefully this new understanding about references will help your coding.
In any case, I agree that references should be used more often than
pointers, but for a different reason. The reason is that references are
restricted compared to pointers, and that restriction is (often) a good
thing. A pointer can be NULL, but a reference cannot (legally) be NULL,
so if you have a function that must not be passed NULL, the easy way to
make that explicit is for the function's parameter to be a reference
rather than a pointer. That way there's one less condition to test for
and one less 'if' at the beginning of your function.


>>>If what you say about inheriting is bad, then I would say my code is 
>>>very foul. I have huge depths of subclassing as a normal part of 
>>>writing my code.
>>
>>Ouch, that's certainly a very dubious design style. It's a typical 
>>hacker's style, and it comes from the Smalltalk world, but it's 
>>generally inappropriate for C++ or Java or any other statically typed 
>>OO language.
>
>Can you point me to resources explaining why this is bad and not just 
>a question of individual style? 

Sure no problem. Start with our book ("C++ FAQs", Addison Wesley), then
go to Scott Meyer's books ("Effective C++" and "More Effective C++",
also Addison Wesley), and probably most any other book that deals with
design/programming style in C++.

>I would have thought it /better/ for 
>statically typed languages because the compiler is given more 
>knowledge with which to optimise.

Nope, it's a very Smalltalk-ish style, and it causes lots of problems in
a statically typed OO language since today's statically typed OO
languages (C++, Java, Eiffel, etc.) equate inheritance with subtyping.
In any language that equates inheritance with subtyping, using
inheritance as a reuse mechanism, as opposed to using inheritance
strictly for subtyping purposes, ultimately causes lots of design and
extensibility problems. It can even effect performance.


>>>It's not just me - I picked up the idea from Qt
>>>(http://www.trolltech.com/). I've basically learned C++ by copying
>>>their example.
>>
>>If you're building an application framework (especially if you're 
>>building a GUI framework), you might have chosen a reasonable piece of
>>software to learn from. If you're simply *using* a framework to build
>>some other app, you've chosen poorly.
>
>I'd like to think I accumulate ideas from whatever existing source I 
>look at. It is after all how I originally taught myself how to 
>program. Again, I'd like to know precisely why this style would be a 
>poor choice for some other app.

Mostly because it creates all sorts of problems for users. Take, for
example, your TSortedList class. You have removed the append() and
prepend() methods because you can't implement them properly in your
class. Nonetheless someone might easily pass an object of your derived
class via pointer or reference to its base class, and within that
function the methods you tried to remove are suddenly available again,
only this time with potentially disastrous results. Take, for example,
this function:

void f(QList<Foo>& x)
{
x.prepend(...); // change '...' to some Foo object
x.append(...); // change '...' to some Foo object
}

Now suppose someone passes a TSortedList object to this function:

void g()
{
TSortedList<Foo> x;
f(x);
...what happens here??
}

In the '...what happens here??' part, anything you do to the TSortedList
is likely to cause problems since the list might not be sorted. E.g.,
if f() adds Foo objects to 'x' in some order other than the sorted
order, then the '...what happens here??' part is likely to cause serious
problems.

You can't blame this problem on references, since the same exact thing
would happen if you changed pass-by-reference to pass-by-pointer.

You can't blame this problem on the C++ compiler, because it can't
possibly detect one of these errors, particularly when the functions f()
and g() were part of two different .cpp files ("compilation units") that
were compiled on different days of the week.

You can't blame this problem on the author of g(), because he believed
the contract of TSortedList. In particular, he believed a TSortedList
was a kind-of a QList. After all that is the meaning of subtyping, and
subtyping is equated in C++ with inheritance. The author of g() simply
believed what you said in this line: 'class TSortedList : public QList',
and you can't blame him for believing what you said.

You can't blame this problem on the author of f(), because he believed
the contract of QList. In particular, he believed he can append()
and/or prepend() values in any order onto any QList. Besides, he wrote
and compiled his code long before you even thought of deriving
TSortedList, and by the rules of extensibility (e.g., see the sections
on Inheritance in the C++ FAQ, or any similar chapters in any book on
the subject), he is not required to predict the future - he is supposed
to be able to write code based on today's realities, and have tomorrow's
subclasses obey today's realities. That is the notion of is-a, and is
codified in many places, including the C++ FAQ, Liskov's
Substitutability Principle ("LSP"), and many other places.

So who is at fault? Ans: the author of TSortedList. Why is the author
of TSortedList at fault? Because of false advertising: he said
TSortedList was a kind-of a QList (or, using precise terminology, that
TSortedList was substitutable for QList), but in the end he violated
that substitutability by removing methods that were promised by QList.

To work a more down-to-earth example, suppose all Plumbers have a
fixPipes() method, and further suppose I claim to be a kind-of Plumber
but I don't have a fixPipes() method. My claim is false: I am not a
kind-of Plumber, but am instead a fraud. Any company that contracted
for my services under my false advertising would be in the right by
claiming I have defrauded them, after all, I claimed to be something I
am not. Similarly a used-car salesman that sells a car which lacks
brakes and/or an engine is in the wrong. Society wouldn't blame the
car, the engine, or the driver; they would blame the salesman for
falsely representing that a particular kind of car is substitutable for
the generally agreed-upon definition of "car." The good news is that in
OO, we don't have to rely on "generally agreed upon definitions."
Instead we look at the base class and that *precisely* defines what is
or is not a "car" (or in this case, a QList).


>>>No all he/she did wrong was assume no one would ever want to subclass
>>>their class. Not only is that arrogant and assumes themselves 
>>>infallible, it seems to me bad practice and against the whole idea of
>>>software reusability.
>>
>>You seem to have a wrong notion of how reusability and inheritance are
>>supposed to mix. Inheritance is not "for" reuse. One does *not* 
>>inherit from something to reuse that thing.
>
>I really don't get you here. I reread the appropriate sections in 
>your FAQ and I *still* don't get this. I can't escape thinking that 
>what I and you mean by "reusing code" is not the same thing 

Agreed: we are saying totally different things for reuse. What I'm
saying is that inheritance is NOT "for" reuse. Inheritance is for
subtyping - for substitutability. Ultimately inheritance is so my code
CAN *BE* reused; not so it can reuse. Has-a (AKA composition AKA
aggregation) is for reuse. Is-a (AKA inheritance AKA substitutability)
is for BEING REUSED.

Put it this way: you inherit from "it" to *be* what it *is*, not simply
to have what it has. If you simply want to have what it has, use has-a
(AKA aggregation AKA composition).

> - for me, 
>whenever you inherit something you inherit its structure (API) and 
>its code - that, to me, is reusing already written and tested code 
>which is a good thing. Hence inheritance = code reuse.

We are totally different here. And unfortunately the extended
experience of a vast majority of C++ programmers has proven your
approach is very short-sighted.

(BTW I will quickly add that your approach is perfectly fine in a very
small project, since in very small projects you can control the damage
of "improper" or "bad" inheritance. Some of my colleagues won't agree
and will say your approach is *always* wrong, and in a sense I would
agree. But from a practical basis, your approach doesn't really cost
too much in the way of time, money, or risk with a small enough project.
If you use your approach on a big project, however, everyone seems to
agree, and everyone's experience seems to prove, that your approach is
very dangerous and expensive.)


>
>>>> Sorry, but this is a very poor solution. It certainly works in a 
>>>> few cases, but it violates the first premise of pluggable software.
>>>> The three solutions described in the FAQ are much better: they 
>>>> handle all
>>>> the trivial cases that yours handles, and in addition they handle
>>>> the
>>>> more sophisticated "plugability" cases.
>>>
>>>I'll review your FAQ's suggestions again. Maybe I missed something, 
>>>but they all seemed to have fairly substantial caveats.
>>
>>They all do. But at least they all solve the pluggability problem, 
>>which is typically the core reason for using this sort of syntax / 
>>technique.
>
>Pluggability = ability to link in or out a "module" of code easily 
>yes?

Yes, sort of. The idea is to add a new feature without changing *any*
existing code -- to add something new without changing any existing .cpp
file, .h file, or any other chunk of code anywhere. Think Netscape
plug-ins or Internet Explorer plug-ins and you'll see what I mean:
people don't need to get a new version of Netscape / IE when some
company creates a new plug-in. People can plug the new plug-in into
their old browser without ANY change to ANY line of code within the
browser itself.


>If so, static class constructs worry me because I can't guarantee 
>their order before main(). 

That's why we use things like construct-on-first-use: so we don't rely
on their order before main().


>My approach solves this, and hence answers 
>the main caveat your FAQ stated.

Yes, but the cost of that benefit is to sacrifice plugability.


>>This is an interesting example. Please do three things:
>>
>>1. Send me the code for template class QList (Qt has a template called
>>QValueList, but I didn't find one called QList).
>
>Yeah Trolltech renamed QList to QPtrList in Qt 3.0.
>
>>2. What is the type
>>of QCollection::Item?
>
>MSVC thinks it's a void *. QGList I included should say more on this.
>
>>3. What is the '___' in TSortedList<____> that
>>caused this error? Or are you saying it always generates an error 
>>independent of any TSortedList<___> usage?? If the latter, better 
>>send me the whole TSortedList template.
>
>It's the latter. I've commented out the original code in what I've 
>sent to get it to compile. If you compare it to QSortedList.h, the 
>two are almost identical (which is intentional) but QSortedList.h 
>compiles perfectly whereas mine stops complaining with:
>
>>d:\atoms\tclient\include\tsortedlist.h(57) : error C2678: binary '=='
>>: no operator defined which takes a left-hand operand of type 'class 
>>type' (or there is no acceptable conversion)
>> d:\atoms\tclient\include\tsortedlist.h(56) : while compiling
>> class-template member function 'int __thiscall
>> TSortedList<class type>::compareItems(void *,void *)'
>>d:\atoms\tclient\include\tsortedlist.h(58) : error C2678: binary '<' :
>>no operator defined which takes a left-hand operand of type 'class 
>>type' (or there is no acceptable conversion)
>> d:\atoms\tclient\include\tsortedlist.h(56) : while compiling
>> class-template member function 'int __thiscall
>> TSortedList<class type>::compareItems(void *,void *)'
>
>This is at template definition, not at template use.

Very, very strange. Are you sure the code was compiled exactly as you
sent it to me? I.e., the definition for compareItems() was within the
class body as you had it in the .h file? I don't have MS VC++ 6
installed right now or I'd check it myself, but on the surface this
seems to be a totally bizarre error message since 'type' is the template
parameter, and therefore the compiler could never check to see if it had
an operator == or < or anything else (including simple assignment!).

In any case, your solution will, unfortunately, cause serious problems
if you use a TSortedList<Foo> when Foo doesn't inherit from
TSortedListItem. If you use TSortedList<Foo> when Foo doesn't inherit
from TSortedListItem, or if Foo multiply inherits from something along
with TSortedListItem, then it will end up calling a rather random
virtual function and it will end up doing rather random things. I would
consider this to be a very fragile piece of code, at best. The compiler
shouldn't stop you from doing what you want it to do.

**DING** I just found your bug. In TSortedList.cpp, all your methods
are listed like this:

int TSortedList<class type>::find( const type *d )
{
...
}

But instead they should be listed like this:

template<class type>
int TSortedList::find( const type *d )
{
...
}

Your syntax ("TSortedList<class type>::") explains everything, including
the bizarre use of 'class type' within the error messages. What
happened is that the compiler saw you using a TSortedList<class type>,
and it therefore tried to compile all the virtual methods within
TSortedList<class type>. When it saw that 'type' really isn't a genuine
class type, it complained (eventually) that the class called 'type'
doesn't have an == or < operator.

When you fix this problem, you will end up with additional problems,
mainly because you have moved template code into a .cpp file. The C++
FAQ covers this issue; suggest you read that for details. (It's
probably in the section on templates and/or containers.)


>>>Hardly important unless Visual Studio .NET has the same problem. MS 
>>>no longer consider MSVC6 a primary support product.
>>
>>Not true. Many companies will continue to use MS VC++ 6 for years to 
>>come. I know a company that's still using MS VC++ version 1.62 for 
>>some of their embedded systems programming.
>
>Companies may continue to use a product, but it's not in Microsoft's 
>commercial interests to encourage them. Based on historical 
>precident, it is extremely clear Microsoft take a bug much more 
>seriously if it's in the current top-of-the-line product. Bugs in 
>older products are more likely to be fixed if (a) new product's fix 
>can be retro-engineered easily and (b) if their reputation would 
>suffer if they didn't. I'm guessing (a) won't apply given the likely 
>substantial redesign to accommodate C#.

My point is that the issue is (was) important to thousands and thousands
and thousands of C++ programmers. Yes Microsoft might choose to ignore
it, but that doesn't mean the issue is no longer relevant or important.


>>>How, might I ask, would you suggest you implement try...finally 
>>>without macros in "the C++ way of doing things"?
>>
>>Use the C++ idiom that "destruction is resource reclamation."
>>
>>>I am assuming you
>>>will surely agree multithreaded programming is not fun without 
>>>try...finally (if you don't, I want reasons, it'll be interesting to
>>>see what you'd come up with).
>>
>>Constructor/destructor <==> resource acquisition/reclamation.
>
>That is a cracking idea I am kicking myself for not having thought of 
>earlier. 

I'm glad I could help.

>I was already concerned about the overhead of throwing an 
>exception every try...finally, but your approach is far simpler and 
>more efficient. It'll require quite a lot of code refitting, but I 
>think it's worth it.
>
>Thank you!

No problem. BTW I consider this an idiom of C++. Part of being
competent in using a language is knowing the syntax and semantics, but
another critical part is knowing the idioms of the language. You're an
expert (I assume) in certain varieties of assembler, and perhaps also in
C. As a competent C programmer, you know the C idioms, such as

while (*dest++ = *src++)
;

This, of course, is the idiom that copies an array of things pointed to
by 'src' into an array pointed to by 'dest', and it stops copying after
it copies the item whose value is zero. If the arrays are arrays of
'char', this is equivalent to strcpy(), since it copies everything
including the terminating '\0'. Obviously other types are similar.

Other idioms in C abound, such as Duff's device:

while (n >= 0) {
switch (n) {
default: xyzzy;
case 7: xyzzy;
case 6: xyzzy;
case 5: xyzzy;
case 4: xyzzy;
case 3: xyzzy;
case 2: xyzzy;
case 1: xyzzy;
}
n -= 8;
}

If you replace 'xyzzy' with some piece of code, this applies that piece
of code exactly 'n' times, but it is much faster than the equivalent:

while (n-- > 0) {
xyzzy;
}

Since the latter executes 'n' decrements, 'n' comparisons, and 'n'
conditional jumps, whereas Duff's device executes only 1/8'th as many
decrements, comparisons, or conditional jumps. Of course the key is
that there is no 'break' statement after each 'case' -- each case "falls
through" to the next case. The other key, obviously, is that the cases
are listed in backwards order.

The point, of course, is that this is another idiom of C, and competent
C programmers know these sorts of things. As you become better and
better at C++, you will learn the idioms of C++, and this is one of
them.


>>>I used to read the reports of the ANSI committee meetings regarding
>>>C++ as it was still being formalised and it always struck me as being
>>>an awful hodge-podge. The more I learn about it, the more I realise I
>>>was correct!
>>
>>Have you ever been on *any* ANSI or ISO standardization committee? If
>>not, it must be easy for you to sit there with zero experience and 
>>throw insults at the hard work of others who have selflessly 
>>sacrificed their time and money to do something big.
>
>I apologise if you interpreted my words as throwing insults for they 
>were not intended as such. 

Apology accepted. I was a little ticked off, but mainly because I get
frustrated when people who really don't know what it's like to steer a
very popular programming language assume they could do a better job.
I'm better now :-)

>I have the utmost respect and admiration 
>for any standardisation committee (with possible exception of the 
>POSIX threads committee, their poor design really screws C++ stack 
>unwinding which is unforgiveable given how recently it was designed).

Not knowing any better, I'd guess they were dealing with very subtle
constraints of existing code or existing practice. Most people on those
sorts of committees are competent, plus the entire world (literally) has
a chance to comment on the spec before it gets finalized, so if there
were gaping holes *that* *could* *be* *fixed* (e.g., without breaking
oodles and oodles of existing code), I'm sure *someone* *somewhere* in
the world would have pointed it out.

Like I said, I don't know the details of this particular situation, but
I would guess that they were fully aware of the problem, that they
investigated all the alternatives, and that they chose the "least bad"
of the alternatives.

>
>However, this does not changed my statement that C++ is an awful 
>hodge-podge. I am not saying everyone involved in standardisation 
>didn't move heaven and earth to make things as good as they could, 
>but with an albatross like keeping existing code compatibility with 
>AT&T C++ and C there was only so much that could be done. I remember 
>the passionate debates about what compromises to strike well.

Yes, existing code puts everyone in a very difficult position, and often
causes compromises. But that's the nature of the beast. The cost of
breaking existing code is much, much greater than canonizing it. Yes
there are compromises with purity, but without those compromises, no one
would use standard C++. After all, there is no law that requires
vendors to implement compilers or libraries that conform to the
standard, so the only way for things to work is for everyone (including
the standardization committees, the compiler vendors, the library
vendors, etc.) to do everything possible to avoid forcing everyone to
rewrite their code. If even 10% of the world's C++ code had to get
rewritten, I predict the C++ standard would get rejected by large
companies and therefore those large companies would ask their vendors to
support the old-fashioned, non-standard syntax/semantics, and all the
good that would have come as a result of having a standard would be for
naught.

>
>Put it this way: when you try something which seems logical in C it 
>generally works the way you think it should. 

Really? No less a light as Dennis Ritchie bemoans the precedence of
some of the operators, and certainly the rather bizarre use of 'static'
has caused more than one C programmer to wonder what's going on. Plus
the issue of order of evaluation, or aliasing, or any number of other
things has caused lots of consternation.

But I guess I agree to this extent: C++ is larger than C, and as such
C++ has more confusing issues. I believe that C99 is causing some of
those same problems, however, since C99 is much bigger than its
predecessor. The same thing will be true of C++0x: it will be bigger
and have more compromises.


>The same in C++ is much 
>less true - I keep finding myself running into limitations which have 
>no good reason. For example, the concept of destination type seems to 
>have no effect in C++ eg;
>
>TQString foo;
>foo="Hello world";
>
>Now TQString is a subclass of QString, and both have const char * 
>ctors. The compiler will refuse to compile the above code because 
>there are two methods of resolving it. 

I may not understand what you mean by "two methods of resolving it," but
I don't understand why the compiler doesn't do what you think it should
above. If TQString has a const char* ctor, then I think that should
promote "Hello world" to a TQString and then use TQString's assignment
operator to change 'foo'.

>Now, to me, that seems stupid 
>because quite clearly the destination type is TQString and the 
>shortest route to that is to use the TQString const char * ctor ie; I 
>clearly am inferring to use the shortest route. The same sort of 
>thing applies to overloading functions - you cannot overload based on 
>return type, something I find particularly annoying.

Another C++ idiom lets you do just that. I'll have to show that one to
you when I have more time. Ask if you're interested.

>
>>>I never had much experience with it, but Objective C
>>>always seemed a lot cleaner.
>>
>>If ObjC is so much better, why is it so unpopular?
>
>Lots of reasons. If I remember correctly, there were many problems 
>with the run-time library on different platforms. There were issues 
>regarding Next and Apple and all that. Of course, as well, there were 
>culture issues - programmer inclinations. Also, there was good 
>competition between many C++ vendors which brought C++ tools to a 
>decent quality pretty quickly.
>
>Computer history is strewn with cases of an inferior product 
>destroying a superior product. It's hardly unique.

I agree. I guess my point is simply this: any popular language is going
to have warts that an unpopular language will not. Take Eiffel for
example. Way back when Eiffel was very young, Bertrand Meyer derided
C++'s 'friend' construct, claiming it violated encapsulation. Then he
began to get real users who were building real systems using Eiffel, and
suddenly he began to see how something like the 'friend' construct
actually *improves* encapsulation. So he added it to Eiffel. At first
the language seemed cleaner and simpler, then gradually it added more
stuff as it became more practical.

C++ is saddled with three basic goals: it tries to be a good procedural
programming language ("C++ as a better C"), and at the same time a good
OO language, and at the same time a good language for programming with
"generics." Trying to be a jack of all trades is difficult, and
ultimately involves compromises. However if you pointed out any
particular compromise, I could probably tell you why it was done and in
fact could (I hope!) make you realize that "cleaning up" that compromise
would cause more harm than good.

In any case, I agree that good products don't always win in the
marketplace.


>>>I take it you come from a Smalltalk background?
>>
>>Not at all. My C++ "smells like" C++, not like Smalltalk or assembler
>>or anything else. Similarly my C code smells like C code, and it uses
>>C idioms, etc., and my Java smells like Java, etc. I am language 
>>neutral, e.g., I've been a member of both the ANSI C++ and ANSI 
>>Smalltalk committees.
>
>In which case you are a better programmer than I. I essentially 
>program the same in any language using an internal methodology and my 
>measure of my liking a language is how little it distorts what I 
>actually want to do (hence my strong dislike of Java and 
>VisualBasic). Nothing I program is what other people call a typical 
>style of that language. You may think that irresponsible and arrogant 
>of me, but I know it is an innate quality of mine - it's the same 
>when I learn human languages (I still retain my own speech formation 
>and pronounciation irrespective).
>
>Hence, I am more of a functional programmer than anything else. 

Do you really mean "functional" or "procedural" here? The Functional
style is rather difficult to do in C++ (think Scheme). Functional
programming means never allowing any changes to any piece of data, so
instead of inserting something into a linked list, one creates a new
linked list and returns the new linked list that contains the new item.

>It is 
>my dream to some day design an imperative/functional hybrid language 
>which would perfectly reflect how I like to program.
>
>>>I know this will
>>>sound approaching blasphemous - and I don't mean at all to be 
>>>offensive, but merely to garner an opinion - but I have always 
>>>considered OO to be a good way of organising maintainable source but
>>>really crap for designing code.
>>
>>Another really big error. OO is primarily a design approach. The 
>>concept of "OO programming" is very close to a misnomer, since OO 
>>programming cannot stand on its own - it needs OO *design*.
>
>No, I must disagree with you there: design is independent of 
>language. 

Nope, not true at all. A design that works for Functional languages is
horrible for Procedural languages, and vice versa. And both those
designs are wholly inappropriate for OO languages, Logic-oriented
languages, or Constraint-oriented languages. In short, the paradigm
*very* much effects the design.

Try your belief out sometime. Try implementing your favorite program in
Prolog (logic-oriented) or Scheme (function-oriented) and see what
happens. Guaranteed that if your program is nontrivial, a radically
different design will emerge. Either that or you'll constantly be
fighting with the paradigm and the underlying language, trying to force,
for example, Prolog to be procedural.

I'll go further: design isn't even independent of language *within* a
paradigm. In other words, a design that is appropriate for Smalltalk is
typically inappropriate for C++, even when you are trying very hard to
use OO thinking throughout.

>I have never agreed with OO design as my university 
>lecturers found out - I quite simply think it's wrong. Computers 
>don't work naturally with objects - it's an ill-fit.
>
>What computers do do is work with data. If you base your design 
>entirely around data, you produce far superior programs. 

In your experience, this may be true. But trust me: it's a big world
out there, and in the *vast* majority of that world, your view is very
dangerous.

Be careful: you are painting yourself into a very narrow corner. You
may end up limiting your career as a result.


>Now I will 
>agree OO is good for organising source for improved maintainability, 
>but as a design approach I think it lacking.

You really should read "OO Design Patterns" by Gamma, et al (also
published by Addison Wesley). Read especially chapter 2. I think
you'll see a whole world of OO design -- and you'll see ways to use OO
at the design level that are totally different (and, I dare say, totally
superior) to the approach you are describing here.

Or take the example of IBM's AS/400. In the early 90s, IBM retained me
to train and mentor all their developers in Rochester MN (and some at
Endicott NY) because they had decided to rewrite the kernel of their
AS/400 operating system using OO design and C++. When we started, they
had a business problem: it took their people 9 months to add certain
features and capabilities. This particular category of "feature and
capability" was added often enough that they wanted to fix that problem.
But being the kernel of an operating system, they couldn't do *anything*
that added *any* overhead.

This project ended up being around half a person-millennium (150-200
developers over a 3 year period). I ended up training and mentoring
them all, and we had lots and lots of design sessions. When they were
finished, the things that used to take 9 months could be done by a
single person in less than a day. The success-story was written up in
Communications of the ACM -- it was the lead article in the Special
Issue on Object-Oriented Experiences. It was also written up in IEEE
Software and perhaps a few other places. (And, by the way, there was no
loss of performance as a result. That was *very* hard to achieve, but
we did it. In the end, customers gained 2x MIPS/dollar.)

The point is that these benefits came as result of OO *design*, not as a
result of programming-level issues.

One more example: UPS (another of my clients; in fact I was there just
last week) has new "rating" and "validation" rules that change every 6
months. For example, if Detroit passes a law saying it's no longer
legal to drive hazardous materials through its downtown area, the code
needs to change to prevent any package containing hazmat from going
through downtown Detroit. In their old system, which was built using
your style of C++, it took 5 months out of every 6 to integrate these
sorts of changes. Then someone created a framework using OO design (not
just C++ programming), and as a result, they could do the same thing in
2 weeks.

Okay, one more - this is the last one -- I promise :-) IBM has an 800
number you can call when you want to buy a ThinkPad or some other
equipment. This division generates *billions* of dollars per year, and
as a result, quality and performance were very important. But
flexibility was also important, because companies like Dell were
promoting their build-to-order systems, and were able to offer
on-the-spot deals that IBM's system couldn't match. Simply put, IBM's
high-performance and high-quality constraints were working against the
system's flexibility, and it was taking IBM wayyyyyy too long to add
promotional deals or other competitive ideas. (Their old approach was
built using non-OO *design* even though it was built using C++.)

When we were done with their system, they could create and install most
changes in minutes. All that without loss of performance and with an
improvement in quality/stability.


>An example: take your typical novice with OO. Tell them the rules and 
>look at what they design. Invariably, pure OO as designed against the 
>rules is as efficient as a one legged dog. 

The way you have learned OO, yes, it will have performance problems.
But the way I am proposing OO should be done, either it won't have
performance problems at all, or if it does, those problems will be
reparable.

>In fact, in my opinion, OO 
>experience is actually learning when to break pure OO and experienced 
>OO advocates do not realise that they so automatically break the pure 
>application of what they advocate.

We agree that purity is never the goal. Pure OO or pure procedural or
pure anything else. The goal is (or *should* be) to achieve the
business objectives. In my experience, OO *design* brings the real
value, and not just programming-level issues.


>A practical example: at university, we had to design a program to 
>sort post office regional codes. The typical class effort, for which 
>they received top marks, sorted the list in about ten to twenty 
>seconds. My effort did it so quickly there wasn't a delay in the 
>command prompt returing - and may I add, I received a bare pass mark 
>because I adopted a data-centric solution and not an OO one. Now I 
>couldn't fault that (the story of my entire degree), but it painfully 
>reminded me of how OO is fundamentally incorrect for computers - good 
>for humans, but not computers.

I agree with everything except your last phrase. OO design is good for
both people and computers.

Marshall




From: Niall Douglas <[email protected]>
To: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Tue, 30 Jul 2002 22:39:24 +0200

On 28 Jul 2002 at 22:31, Marshall Cline wrote:

Firstly, may I ask your permission to distribute a digest of our 
conversation to others? I believe quite a few people could do with 
reading it because (and this may worry you) I am considered one of 
the better C++ people out of our class' graduates. If I didn't know, 
I'm very sure they didn't simply because it was never taught.

> I spoke last time about being a prisoner of our pasts. My past
> includes acting as "senior technology consultant" to IBM throughout
> North America, which meant advising on product strategy, mentoring,
> and (most relevant to this situation) performing internal audits. The
> audits included a number of important engagements with IBM's clients,
> and required me to perform assessments of people and technology. 
> During these audits and assessments, I saw a lot of large projects
> that failed because of overengineering. Many of the technologists on
> these sick or dead projects had a similar perspective to what you
> articulated above. Their basic approach was often that overengineering
> is better than underengineering, that it's cheaper in the long run,
> and perhaps cheaper in the short run, so let's overengineer just in
> case.

I think there are two types of overengineering: controlled and 
uncontrolled. The latter happens when the people doing the design 
aren't really sure what they're doing. The former happens when the 
designers take into proper account the likely extensions in the 
future, possible client changes in specification, ramifications on 
maintainability etc. and balance all of these against time of 
implementation, worth to the project etc. Essentially, what I am 
really saying, is if you spend plenty of time on *design* then your 
project comes in on time and within budget.

BTW, have you heard of extreme programming 
(http://www.extremeprogramming.org/)? Daft name, but it's an 
interesting looking way of managing and overseeing computer projects. 
It certainly is less intrusive than auditing, and establishes more 
trust between customer and provider.

> As a result of seeing in excess of one hundred million dollars worth
> of effort (and numerous careers) washed down the drain, I tend to make
> sure there is a realistic ROI before adding any effort that has a
> future-payback.

Again I think we're saying precisely the same thing with different 
words.

Let me give you a bit of background on myself (helps later on): My 
role in recent years is saving troubled projects. I am brought in 
when things have gone horribly wrong - for example, my last two 
positions were saving a handheld GPS project in Canada and saving a 
EuroFighter component test bench control software project here in 
Spain. Usually, I come in, assess the situation (code, employees and 
most importantly management) and fix it. In both projects, I have 
been spectacularly successful, albeit at the cost of my own job - to 
save a troubled project you need to work on many areas, but the most 
obstinate in my experience is management who employ a "pass the buck" 
methodology whilst firing good programmers to divert the blame. In 
the end, I always come up against what was killing the project 
beforehand, at which stage it's time to move on.

However, my background is in a little British computer called an 
Acorn which ran on ARM processors (nowadays Acorn is liquidated and 
ARM, its offshoot, is one of the bigger UK companies). Acorn's ran an 
OS called RISC-OS which was the last general purpose all-assembler OS 
ever written. And I will tell you now, it was vastly ahead of 
anything else at the time - and I include Unix. Obviously, everything 
in the system was designed around writing in assembler, and hence 
large applications (DTP, editors, spreadsheets, photo-editing, music 
composition etc.) often were entirely written in hand-coded ARM. 
Hence all of us did stuff which most people consider unlikely in 
assembler - for example, we used what you could call an object in 
that some code would have instance data and a ctor and destructor. We 
had the equivalent of virtual functions using API offset tables. Some 
silly people used self-modifying code, which is worse that goto's 
IMHO.

What is important to get from this is that until the US 
multinationals crushed our indigenous European computer industry, we 
were in many ways considerably ahead of the status quo. This is why I 
don't fit easily into boxes others like to assign me to.

> >>Ouch, that's certainly a very dubious design style. It's a typical
> >>hacker's style, and it comes from the Smalltalk world, but it's
> >>generally inappropriate for C++ or Java or any other statically
> >>typed OO language.
> >
> >Can you point me to resources explaining why this is bad and not just
> > a question of individual style? 
> 
> Sure no problem. Start with our book ("C++ FAQs", Addison Wesley),
> then go to Scott Meyer's books ("Effective C++" and "More Effective
> C++", also Addison Wesley), and probably most any other book that
> deals with design/programming style in C++.

Not being able to obtain these books easily (I live in Spain plus 
money is somewhat tight right now), I looked around the web for more 
on this. I specifically found what not to do when inheriting plus how 
deep subclassing usually results in code coupling increasing. Is that 
the general gist?

> >I would have thought it /better/ for 
> >statically typed languages because the compiler is given more 
> >knowledge with which to optimise.
> 
> Nope, it's a very Smalltalk-ish style, and it causes lots of problems
> in a statically typed OO language since today's statically typed OO
> languages (C++, Java, Eiffel, etc.) equate inheritance with subtyping.
> In any language that equates inheritance with subtyping, using
> inheritance as a reuse mechanism, as opposed to using inheritance
> strictly for subtyping purposes, ultimately causes lots of design and
> extensibility problems. It can even effect performance.

In other words, precisely the trap I was falling myself into. I 
should however mention that having examined my code, I was peforming 
this trap only in the areas where Qt wasn't providing what I needed. 
In the code generated entirely by myself, I tend to use a top-down 
approach with an abstract base class defining the reusable parts.

I should mention that much of the subclassing I have had to do will 
disappear with future versions of Qt as they have very kindly mostly 
agreed with my ideas. Hence, in fact, until v4.0, it's mostly stop-
gap code.

> >Again, I'd like to know precisely why this style would be a
> >poor choice for some other app.
> 
> Mostly because it creates all sorts of problems for users. Take, for
> example, your TSortedList class. You have removed the append() and
> prepend() methods because you can't implement them properly in your
> class. Nonetheless someone might easily pass an object of your
> derived class via pointer or reference to its base class, and within
> that function the methods you tried to remove are suddenly available
> again, only this time with potentially disastrous results. Take, for
> example, this function:
> 
> void f(QList<Foo>& x)
> {
> x.prepend(...); // change '...' to some Foo object
> x.append(...); // change '...' to some Foo object
> }
> 
> Now suppose someone passes a TSortedList object to this function:
> 
> void g()
> {
> TSortedList<Foo> x;
> f(x);
> ...what happens here??
> }

Err, prepend and append aren't virtual in the base class, so the base 
class' versions would be called. I had realised previously that it's 
a very bad idea to disable virtual inherited methods - or if you were 
to, you'd want a fatal exception in there to trap during debug.

> In the '...what happens here??' part, anything you do to the
> TSortedList is likely to cause problems since the list might not be
> sorted. E.g., if f() adds Foo objects to 'x' in some order other than
> the sorted order, then the '...what happens here??' part is likely to
> cause serious problems.
> 
> You can't blame this problem on references, since the same exact thing
> would happen if you changed pass-by-reference to pass-by-pointer.
> 
> You can't blame this problem on the C++ compiler, because it can't
> possibly detect one of these errors, particularly when the functions
> f() and g() were part of two different .cpp files ("compilation
> units") that were compiled on different days of the week.
> 
> You can't blame this problem on the author of g(), because he believed
> the contract of TSortedList. In particular, he believed a TSortedList
> was a kind-of a QList. After all that is the meaning of subtyping,
> and subtyping is equated in C++ with inheritance. The author of g()
> simply believed what you said in this line: 'class TSortedList :
> public QList', and you can't blame him for believing what you said.
> 
> You can't blame this problem on the author of f(), because he believed
> the contract of QList. In particular, he believed he can append()
> and/or prepend() values in any order onto any QList. Besides, he
> wrote and compiled his code long before you even thought of deriving
> TSortedList, and by the rules of extensibility (e.g., see the sections
> on Inheritance in the C++ FAQ, or any similar chapters in any book on
> the subject), he is not required to predict the future - he is
> supposed to be able to write code based on today's realities, and have
> tomorrow's subclasses obey today's realities. That is the notion of
> is-a, and is codified in many places, including the C++ FAQ, Liskov's
> Substitutability Principle ("LSP"), and many other places.
> 
> So who is at fault? Ans: the author of TSortedList. Why is the
> author of TSortedList at fault? Because of false advertising: he said
> TSortedList was a kind-of a QList (or, using precise terminology, that
> TSortedList was substitutable for QList), but in the end he violated
> that substitutability by removing methods that were promised by QList.

Worse I think would be redefining inherited methods to do something 
completely different. But yes, I understand now.

The rule should be that subclasses must always behave like their base 
class(es). Code reuse should be done via composition.

Hence, that TSortedList should now derive off QGList which doesn't 
have the append and prepend methods so I can safely ensure it does 
what its parent does.

> Put it this way: you inherit from "it" to *be* what it *is*, not
> simply to have what it has. If you simply want to have what it has,
> use has-a (AKA aggregation AKA composition).

Yes, I definitely understand you now. It is a pity explanations like 
this weren't more universally available, because I know a lot of C++ 
programmers learned from MSVC's online help (eg; initially me - it's 
where I learned C from as well). I however did subscribe to the 
Association of C & C++ Users which is why I know about 
standardisation debates - but even though at the time I subscribed 
for the C coverage, I did read the C++ sections.

A lot of people say pointers are the devil's tool in C and I have met 
a disturbing number of programmers who just don't understand them. 
However, it seems to me pointers are child's play in unenforced 
danger when compared to problems like you and your FAQ have 
mentioned. If more warning were out there, we'd all have less 
problems with other people's code.

> (BTW I will quickly add that your approach is perfectly fine in a very
> small project, since in very small projects you can control the damage
> of "improper" or "bad" inheritance. Some of my colleagues won't agree
> and will say your approach is *always* wrong, and in a sense I would
> agree. But from a practical basis, your approach doesn't really cost
> too much in the way of time, money, or risk with a small enough
> project. If you use your approach on a big project, however, everyone
> seems to agree, and everyone's experience seems to prove, that your
> approach is very dangerous and expensive.)

Unfortunately what I am working on now I expect to exceed 100,000 
lines before I'll consider it reasonably done. I'll explain later.

> [content clipped]
> **DING** I just found your bug. In TSortedList.cpp, all your methods
> are listed like this:
> [code chopped]
> Your syntax ("TSortedList<class type>::") explains everything,
> including the bizarre use of 'class type' within the error messages. 
> What happened is that the compiler saw you using a TSortedList<class
> type>, and it therefore tried to compile all the virtual methods
> within TSortedList<class type>. When it saw that 'type' really isn't
> a genuine class type, it complained (eventually) that the class called
> 'type' doesn't have an == or < operator.

I don't see how the syntax is consistent then. From what I can see 
template<pars> X where X is the code to be parametrised - or are you 
saying I declare the methods in the class definition and move the 
code to inline void TSortedList<class type>::foo() after the template 
class definition?

Either way, this was my first ever template class (yes, in all three 
years of using C++) and I copied heavily off QSortedList.h (which I 
enclosed last time) which might I point out compiles absolutely fine. 
So why my class, almost identical, does not and Qt's one does I do 
not know.

> When you fix this problem, you will end up with additional problems,
> mainly because you have moved template code into a .cpp file. The C++
> FAQ covers this issue; suggest you read that for details. (It's
> probably in the section on templates and/or containers.)

It says you can't move template code into a .cpp file :)

I think you can just put it in the header file though? I'm still not 
sure why it threw an error :(

> >[a bloody good suggestion]
> >Thank you!
> 
> No problem. BTW I consider this an idiom of C++. Part of being
> competent in using a language is knowing the syntax and semantics, but
> another critical part is knowing the idioms of the language. You're
> an expert (I assume) in certain varieties of assembler, and perhaps
> also in C. As a competent C programmer, you know the C idioms, such
> as
> 
> while (*dest++ = *src++)
> ;
> 
> This, of course, is the idiom that copies an array of things pointed
> to by 'src' into an array pointed to by 'dest', and it stops copying
> after it copies the item whose value is zero. If the arrays are
> arrays of 'char', this is equivalent to strcpy(), since it copies
> everything including the terminating '\0'. Obviously other types are
> similar.

That kind of dangerous code brought out a compiler bug in a version 
of GCC and MSVC 5 if I remember correctly. The increments weren't 
always done with the load and store when full optimisation was on. 
Solution: use comma operator.

> Other idioms in C abound, such as Duff's device:
> 
> while (n >= 0) {
> switch (n) {
> default: xyzzy;
> case 7: xyzzy;
> case 6: xyzzy;
> case 5: xyzzy;
> case 4: xyzzy;
> case 3: xyzzy;
> case 2: xyzzy;
> case 1: xyzzy;
> }
> n -= 8;
> }
> 
> If you replace 'xyzzy' with some piece of code, this applies that
> piece of code exactly 'n' times, but it is much faster than the
> equivalent:
> 
> while (n-- > 0) {
> xyzzy;
> }
> 
> Since the latter executes 'n' decrements, 'n' comparisons, and 'n'
> conditional jumps, whereas Duff's device executes only 1/8'th as many
> decrements, comparisons, or conditional jumps. Of course the key is
> that there is no 'break' statement after each 'case' -- each case
> "falls through" to the next case. The other key, obviously, is that
> the cases are listed in backwards order.

This is called loop unrolling in assembler and I thought compilers 
did it for you because modern processors run so much faster than 
system memory that the compsci measurement of execution time is often 
way way off - smaller code on modern processors goes faster than 
bigger code, even with loads of pipeline flushes from the conditional 
branches because the L1 cache is 10x system memory speed.

> The point, of course, is that this is another idiom of C, and
> competent C programmers know these sorts of things. As you become
> better and better at C++, you will learn the idioms of C++, and this
> is one of them.

Actually, using a switch() statement is bad on modern deep pipeline 
processors. It's better to use a function pointer table and calculate 
the index because then the data cache effectively maintains a branch 
history for you.

If you ask me about embedded systems, I don't doubt I'm as good as 
they get. All this high-level stuff though I must admit is beyond me 
a bit. But more on that later.

> >I have the utmost respect and admiration 
> >for any standardisation committee (with possible exception of the
> >POSIX threads committee, their poor design really screws C++ stack
> >unwinding which is unforgiveable given how recently it was designed).
> 
> Not knowing any better, I'd guess they were dealing with very subtle
> constraints of existing code or existing practice. 

Twas a completely new API AFAIK.

> Most people on
> those sorts of committees are competent, plus the entire world
> (literally) has a chance to comment on the spec before it gets
> finalized, so if there were gaping holes *that* *could* *be* *fixed*
> (e.g., without breaking oodles and oodles of existing code), I'm sure
> *someone* *somewhere* in the world would have pointed it out.

From the usenet posts I've read, people did point out POSIX thread 
cancellation did not offer C++ an opportunity to unwind the stack, 
but they ignored it and went with a setjump/longjmp solution instead. 
Now if your platform's setjmp implementation unwinds the stack - 
fantastic. If not, severe memory leakage. All they needed was the 
ability to set a function to call to perform the cancellation - under 
C++, that would be best done by throwing an exception. Still, we can 
hope it will be added in the near future.

> Like I said, I don't know the details of this particular situation,
> but I would guess that they were fully aware of the problem, that they
> investigated all the alternatives, and that they chose the "least bad"
> of the alternatives.

The above support for C++ (and other languages) costs maybe an hour 
to add and is completely portable. I can't see how it wasn't done by 
competent and fair designers. I have read rabid posts on usenet why 
you shouldn't be writing mulithreaded C++ and such bollocks - not 
sure if that's involved. The whole issues of threads gets many Unix 
people's knickers in a right twist - they seem to think they're 
"wrong", much like exceptions are "wrong" in C++ for some people. 
Weird.

> Yes, existing code puts everyone in a very difficult position, and
> often causes compromises. But that's the nature of the beast. The
> cost of breaking existing code is much, much greater than canonizing
> it. [clipped rest]

Have you noticed the world's most popular programming languages tend 
to be evolved rather than designed? ;)

> >Put it this way: when you try something which seems logical in C it
> >generally works the way you think it should. 
> 
> Really? No less a light as Dennis Ritchie bemoans the precedence of
> some of the operators, and certainly the rather bizarre use of
> 'static' has caused more than one C programmer to wonder what's going
> on. Plus the issue of order of evaluation, or aliasing, or any number
> of other things has caused lots of consternation.

Yeah, I've read his comments. There's one operator in particular - is 
it &&? - which is very suspect in precedence.

However, that said, once you get used to the way of C logic it stays 
remarkably consistent. I personally recommend sticking "static" 
before everything you want to be static and don't rely on default 
behaviour - people's confusion with static clears up remarkably 
quickly if you do that.

> But I guess I agree to this extent: C++ is larger than C, and as such
> C++ has more confusing issues. I believe that C99 is causing some of
> those same problems, however, since C99 is much bigger than its
> predecessor. The same thing will be true of C++0x: it will be bigger
> and have more compromises.

It's also a case of history. K&R C was 90% done by just a few guys. 
C++ is a collection of different enhancements over C by completely 
different people with different intentions, and then with a good 
dollop of academic theory thrown in for good measure. Hence its non-
uniformity and lack of consistency.

Note that I have not yet found an academic who thinks C is an 
excellent example of a procedural language :)

> >TQString foo;
> >foo="Hello world";
> >
> >Now TQString is a subclass of QString, and both have const char *
> >ctors. The compiler will refuse to compile the above code because
> >there are two methods of resolving it. 
> 
> I may not understand what you mean by "two methods of resolving it,"
> but I don't understand why the compiler doesn't do what you think it
> should above. If TQString has a const char* ctor, then I think that
> should promote "Hello world" to a TQString and then use TQString's
> assignment operator to change 'foo'.

I completely agree. However, MSVC wants you to put a TQString("Hello 
world") around every const char * :(

I'm running into similar problems with the << and >> operators - I've 
subclassed QDataStream with TQDataStream because QDataStream is 
default big endian and doesn't provide support for 64 bit integers. 
Every single time I use << or >> I'm an ambiguous resoluton error 
when clearly the source or destination object is a TQDataStream.

Most of my C++ problems are arising from "repairing" Trolltech's 
code. While they will fix things similarly in future Qt's as a result 
of my suggestions, it still leaves now.

> > The same sort of
> >thing applies to overloading functions - you cannot overload based on
> > return type, something I find particularly annoying.
> 
> Another C++ idiom lets you do just that. I'll have to show that one
> to you when I have more time. Ask if you're interested.

Is that like this:
bool node(TQString &dest, u32 idx)
bool node(TKNamespaceNodeRef &ref, u32 idx)
...

> >Computer history is strewn with cases of an inferior product 
> >destroying a superior product. It's hardly unique.
> 
> I agree. I guess my point is simply this: any popular language is
> going to have warts that an unpopular language will not. Take Eiffel
> for example. Way back when Eiffel was very young, Bertrand Meyer
> derided C++'s 'friend' construct, claiming it violated encapsulation. 
> Then he began to get real users who were building real systems using
> Eiffel, and suddenly he began to see how something like the 'friend'
> construct actually *improves* encapsulation. So he added it to
> Eiffel. At first the language seemed cleaner and simpler, then
> gradually it added more stuff as it became more practical.

Still, Eiffel is considered much cleaner than C++ - however, it's not 
as popular. cf. my statement above about popular languages not being 
designed.

> However if you
> pointed out any particular compromise, I could probably tell you why
> it was done and in fact could (I hope!) make you realize that
> "cleaning up" that compromise would cause more harm than good.

Ok:
1. Why didn't C++ have separated support for code reuse and subtyping 
(like Smalltalk)?
2. Why don't return types determine overload?
3. Why can't the compiler derive non-direct copy construction? eg;
class A { A(B &); } class B { B(C &}; } class C { C(const char *); }
A foo="Hello";

In C++, you must rewrite that as A foo(B(C("Hello"))); - it's not 
done for you, nor is there any way of fixing it except modifying A to 
have a copy constructor taking const char * - which isn't possible if 
you don't have the source to A or B.

> Do you really mean "functional" or "procedural" here? The Functional
> style is rather difficult to do in C++ (think Scheme). Functional
> programming means never allowing any changes to any piece of data, so
> instead of inserting something into a linked list, one creates a new
> linked list and returns the new linked list that contains the new
> item.

I mean "functional" in terms of I tell you what to do not how to do 
it. 

Also above, the new linked list isn't created, merely a potential for 
a separate new linked list is. You're right that it's "as if".

> >>Another really big error. OO is primarily a design approach. The
> >>concept of "OO programming" is very close to a misnomer, since OO
> >>programming cannot stand on its own - it needs OO *design*.
> >
> >No, I must disagree with you there: design is independent of 
> >language. 
> 
> Nope, not true at all. A design that works for Functional languages
> is horrible for Procedural languages, and vice versa. And both those
> designs are wholly inappropriate for OO languages, Logic-oriented
> languages, or Constraint-oriented languages. In short, the paradigm
> *very* much effects the design.

I would have said it affects the /implementation/ rather than the 
design. You're right that say doing a sort in Haskell is completely 
different than doing it in C - but I would call that a difference in 
implementation because (a) the algorithm used is identical and (b) 
the two solutions give identical output.

> Try your belief out sometime. Try implementing your favorite program
> in Prolog (logic-oriented) or Scheme (function-oriented) and see what
> happens. Guaranteed that if your program is nontrivial, a radically
> different design will emerge. Either that or you'll constantly be
> fighting with the paradigm and the underlying language, trying to
> force, for example, Prolog to be procedural.

No, a radically different /implementation/ will emerge. That's simply 
because implementing the design is best done one way in one language 
and differently in a different language.

> I'll go further: design isn't even independent of language *within* a
> paradigm. In other words, a design that is appropriate for Smalltalk
> is typically inappropriate for C++, even when you are trying very hard
> to use OO thinking throughout.

I'm getting a feeling that once again it's a disagreement about 
terminology rather than opinion. I would treat the word "design" as 
that in its purest sense - algorithms. Everything after that has 
increasing amounts of implementation - so, for example, the object 
structure would involve some amount of implementation detail.

> >I have never agreed with OO design as my university 
> >lecturers found out - I quite simply think it's wrong. Computers
> >don't work naturally with objects - it's an ill-fit.
> >
> >What computers do do is work with data. If you base your design
> >entirely around data, you produce far superior programs. 
> 
> In your experience, this may be true. But trust me: it's a big world
> out there, and in the *vast* majority of that world, your view is very
> dangerous.

I have applied my skills to many projects: public, private and 
personal and I have not found my data-centric approach to have failed 
yet. It has nothing to do with code maintainability nor much other 
than efficiency - but that's why I use an impure OO for 
maintainability - but if you rate superiority of a program based on 
its excellence in functioning, my approach works very well. I 
contrast with OO designed projects and quite simply, on average they 
do not perform as well.

Now regarding the TCO of the code, I would personally say my code is 
extremely maintainable using my OO-like source filing system. You, I 
would imagine, would say how can I sleep at night when performing 
such atrocities to commonly held standards? (you wouldn't be the 
first to ask this).

Of course, in all this, I am referring to C and assembler and what 
I'd call C+ because I mostly wrote C with some small C++ extras. This 
project I'm working on now is the first to use multiple inheritance 
and templates and a fair bit more.

> Be careful: you are painting yourself into a very narrow corner. You
> may end up limiting your career as a result.

Possibly, but I would doubt it. I may have some unique opinions on 
this but what the customer cares about is (a) will it work and (b) 
can we look after it well into the future. My case history strongly 
supports both of these criteria, so a priori I'm on the right path.

> >Now I will 
> >agree OO is good for organising source for improved maintainability,
> >but as a design approach I think it lacking.
> 
> You really should read "OO Design Patterns" by Gamma, et al (also
> published by Addison Wesley). Read especially chapter 2. I think
> you'll see a whole world of OO design -- and you'll see ways to use OO
> at the design level that are totally different (and, I dare say,
> totally superior) to the approach you are describing here.

Is that about a vector graphics editor called Lexi? I have written 
two vector graphic editors, the latter in OPL for a Psion Series 3 
(OPL is quite like BASIC - no objects). Interestingly, the approach 
Gamma follows is almost identical to my own - I used dynamic code 
loading to load tool modules with a fixed API thus permitting 
infinite extensibility. Encapsulation of the API plus building a 
portable framework are two things I have done many times - I wrote my 
first framework library in 1992 some four years before going 
professional.

That Gamma book amused me - it attaches lots of fancy names to real 
cabbage and thistle programming. However, his conclusion is valid - 
in modern times, most programmers wouldn't know half that book, and 
that's worrying - hence the need for such a book.

> This project ended up being around half a person-millennium (150-200
> developers over a 3 year period). I ended up training and mentoring
> them all, and we had lots and lots of design sessions. When they were
> finished, the things that used to take 9 months could be done by a
> single person in less than a day. The success-story was written up in
> Communications of the ACM -- it was the lead article in the Special
> Issue on Object-Oriented Experiences. It was also written up in IEEE
> Software and perhaps a few other places. (And, by the way, there was
> no loss of performance as a result. That was *very* hard to achieve,
> but we did it. In the end, customers gained 2x MIPS/dollar.)
> 
> The point is that these benefits came as result of OO *design*, not as
> a result of programming-level issues.

I'm sure OO design greatly improved the likely wasp's nest of 
spaghetti that existed in there previously. But I'm not seeing how OO 
design is better than any other approach from this example - there 
are many methods that could have been employed to achieve the same 
result.

> One more example: UPS (another of my clients; in fact I was there just
> last week) has new "rating" and "validation" rules that change every 6
> months. For example, if Detroit passes a law saying it's no longer
> legal to drive hazardous materials through its downtown area, the code
> needs to change to prevent any package containing hazmat from going
> through downtown Detroit. In their old system, which was built using
> your style of C++, it took 5 months out of every 6 to integrate these
> sorts of changes. Then someone created a framework using OO design
> (not just C++ programming), and as a result, they could do the same
> thing in 2 weeks.

Any good framework here, OO or not, would have solved most of their 
dynamic change problem. In fact, I'd plug in some sort of scripting 
capability so such items were easy to change.

> >An example: take your typical novice with OO. Tell them the rules and
> > look at what they design. Invariably, pure OO as designed against
> >the rules is as efficient as a one legged dog. 
> 
> The way you have learned OO, yes, it will have performance problems.
> But the way I am proposing OO should be done, either it won't have
> performance problems at all, or if it does, those problems will be
> reparable.

ie; You're bending OO to suit real-world needs, which is precisely 
what I said experienced OO people do.

> >In fact, in my opinion, OO 
> >experience is actually learning when to break pure OO and experienced
> > OO advocates do not realise that they so automatically break the
> >pure application of what they advocate.
> 
> We agree that purity is never the goal. Pure OO or pure procedural or
> pure anything else. The goal is (or *should* be) to achieve the
> business objectives. In my experience, OO *design* brings the real
> value, and not just programming-level issues.

Has it not occurred to you that it's merely a /consequence/ of OO 
rather than innate quality that it has these beneficial effects?

> I agree with everything except your last phrase. OO design is good
> for both people and computers.

Right, firstly, before I start this section, I'd like to thank you 
for your time and patience - I've noticed some of what I didn't know 
and you explained to me was already online in your FAQ, so I 
apologise for wasting your time in this regard. Furthermore, I should 
mention that if you give me permission to distribute this 
correspondence, you will not only have done me a great favour but 
also the same to others. Certainly, if it takes you as long to reply 
as me, you're investing considerable time which a busy man as 
yourself surely cannot easily spare.

I, as I have already mentioned, come from a rather unique programming 
background. We were probably most comparable to the Unix culture 
except we were more advanced and we always had a very strong free 
software tradition where we released code and source into the public 
domain - furthermore, many commercial apps came with source too. 
Hence, there was great chance for learning off others, and much of 
this recent furore about OO etc. in my humble opinion is merely fancy 
names for a collection of old techniques.

Now as I mentioned a number of times, I believe a data-centric 
approach is superior to OO because it more accurately fits the way a 
computer works. This is not to say many of the advantages of OO do 
not still hold - in fact, I daresay many OO experts actually are data-
centric too without realising it. My criticism of OO therefore is 
that it isn't /intuitively/ "correct" ie; pure OO is rarely the 
optimal solution.

I had an idea back in 1994 for advancing procedural programming to 
the next level (this was independent of OO - honestly, I barely even 
knew what it was at the time) - I effectively wanted to do what OO 
has done in knocking us onwards a notch - however, as it would be, I 
considered then and still do today that my solution is superior.

Basically, it revolves entirely around data. Responsibility for data, 
whether in memory, disc or across a network is devolved entirely to 
the kernel. One may create data streams between data in an arbitrary 
fashion - how it actually is peformed (translations etc.) is however 
the kernel sees fit. Data is strongly typed so you can't stick 
incompatible types of data together - however data can be converted 
from one type to another via convertors which are essentially 
specialised plug ins which can be installed. Often, conversion is 
implicitly performed for you although either you can choose a route 
or it can dynamically create one based on best past performances. Of 
course, converters can offer out their input in more than one format 
or indeed offer a compound document as some or all of its subdatas.

Now the next part of the picture is components - these are tiny 
programs which do one thing and one thing well to data. A good 
analogy would be "more" or "grep" in Unix - but it goes way beyond 
that because components are much like a COM object or Qt Widget in 
that you can just plonk them somewhere and they do their thing. Then, 
the theory is, to build any application, you merely create a *web* of 
simple data processing components. For example, a spell checker 
component would accept text data and check it either with the user or 
with the component managing the data - there is no concept of data 
ownership in my proposal (kernel owns everything)

This model, I believe, compares extremely well to OO. You get lots of 
code reuse, a dynamic and extremely flexible linking mechanism, a web 
rather than a hierarchy and automatic distribution across multiple 
processors (and indeed machines). It's clearly functionally biased 
because it simply sets up the data relations and the kernel works out 
the best way to actually perform the processing. You get lots of 
stuff for free eg; OLE, data recovery in case of program crash and 
indeed limited graphical programming like some of those UML editors. 
You get the advantages of dynamic linking without business' dislike 
of source exposure as with Java or VB.

Furthermore, you get automatic /data/ reuse as well as code reuse - 
data just as much as code can be distributed across multiple machines 
for performance and/or security reasons. And of course, maintainence 
costs are low because the component set you use are as individual or 
fine-grained as you like them.

Now hopefully you'll be agreeing with me that this is all good - 
however, if you're like the other experts I've proposed this to, your 
first question will be "oh but how to implement it?" because the 
balancing act between all the different requirements means severe 
inefficiency. And you'd be right - I've made two prior attempts at 
this and failed both times - and right now, I'm making my third 
attempt which I'm self-financing myself for six months. The theory 
goes, produce a technology demonstration, if it runs at all 
reasonably then obtain venture capital, start a company and two years 
later we have a product. Five years later it's more or less complete. 
If anything goes wrong, return to working on whatever pays a lot for 
a while, then try again in a few years. Either way, the spin off 
benefits of each past attempt have been enormous, so really I can't 
lose.

So, thoughts? I'm particularly interested in what you see as design 
flaws - I know MIT did research into this for a while but stopped. 
Would you agree it's a viable future? I've had Carl Sassenrath (he 
did much the OS for the Commodore Amiga) and Stuart Swales (did much 
of RISC-OS I mentioned earlier) both agree it's probably right, but 
both wondered about implementation. I should be especially interested 
in seeing what a static OO based person things - neither Carl nor 
Stuart are static code nor OO advocates hugely.

Furthermore, any advice about soliciting venture capital in Europe 
would be useful (yes, I know it's like squeezing blood from a stone 
here) - ever since the indigenous industry withered and died here, 
it's been very hard to obtain capital for blue-sky projects without 
the Americans buying them up. I'm unable to obtain a work visa to the 
US (on the banned list), so that's out - and besides, as far as I can 
see, only IBM out of the big US software companies would be 
interested as only IBM's goals would be advanced by such a project. 
Oh BTW, did I mention it runs on Win32/64, Linux and MacOS X when 
they get the new FreeBSD kernel in - and yes, all computer 
irrespective of endian automatically work in unison. I'd also like it 
to stay in Europe so it (or rather I) stays free from software 
patents.

Anyway, any comments you may like to offer would be greatly 
appreciated. You've already earned yourself an acknowledgement in the 
projects docs for helpful tips and suggestions.

Cheers,
Niall




From: "Marshall Cline" <[email protected]>
To: "'Niall Douglas'" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Tue, 30 Jul 2002 02:14:13 -0500

Niall Douglas wrote:
>On 28 Jul 2002 at 22:31, Marshall Cline wrote:
>
>Firstly, may I ask your permission to distribute a digest of our 
>conversation to others? I believe quite a few people could do with 
>reading it because (and this may worry you) I am considered one of 
>the better C++ people out of our class' graduates. If I didn't know, 
>I'm very sure they didn't simply because it was never taught.

Sure, no prob.


>>I spoke last time about being a prisoner of our pasts. My past 
>>includes acting as "senior technology consultant" to IBM throughout 
>>North America, which meant advising on product strategy, mentoring, 
>>and (most relevant to this situation) performing internal audits. The
>>audits included a number of important engagements with IBM's clients, 
>>and required me to perform assessments of people and technology. 
>>During these audits and assessments, I saw a lot of large projects 
>>that failed because of overengineering. Many of the technologists on 
>>these sick or dead projects had a similar perspective to what you 
>>articulated above. Their basic approach was often that overengineering
>>is better than underengineering, that it's cheaper in the long run, 
>>and perhaps cheaper in the short run, so let's overengineer just in 
>>case.
>
>I think there are two types of overengineering: controlled and 
>uncontrolled. The latter happens when the people doing the design 
>aren't really sure what they're doing. The former happens when the 
>designers take into proper account the likely extensions in the 
>future, possible client changes in specification, ramifications on 
>maintainability etc. and balance all of these against time of 
>implementation, worth to the project etc. Essentially, what I am 
>really saying, is if you spend plenty of time on *design* then your 
>project comes in on time and within budget.

Agreed.


>BTW, have you heard of extreme programming 
>(http://www.extremeprogramming.org/)? 

Yes, though I tend to prefer its umbrella concept, Agile programming.


>Daft name, but it's an 
>interesting looking way of managing and overseeing computer projects. 
>It certainly is less intrusive than auditing, and establishes more 
>trust between customer and provider.
>
>>As a result of seeing in excess of one hundred million dollars worth 
>>of effort (and numerous careers) washed down the drain, I tend to make
>>sure there is a realistic ROI before adding any effort that has a 
>>future-payback.
>
>Again I think we're saying precisely the same thing with different 
>words.
>
>Let me give you a bit of background on myself (helps later on): My 
>role in recent years is saving troubled projects. I am brought in 
>when things have gone horribly wrong - for example, my last two 
>positions were saving a handheld GPS project in Canada and saving a 
>EuroFighter component test bench control software project here in 
>Spain. Usually, I come in, assess the situation (code, employees and 
>most importantly management) and fix it. In both projects, I have 
>been spectacularly successful, albeit at the cost of my own job - to 
>save a troubled project you need to work on many areas, but the most 
>obstinate in my experience is management who employ a "pass the buck" 
>methodology whilst firing good programmers to divert the blame. 

Sigh - what a shame. I've seen that too many times.

>In 
>the end, I always come up against what was killing the project 
>beforehand, at which stage it's time to move on.
>
>However, my background is in a little British computer called an 
>Acorn which ran on ARM processors (nowadays Acorn is liquidated and 
>ARM, its offshoot, is one of the bigger UK companies). Acorn's ran an 
>OS called RISC-OS which was the last general purpose all-assembler OS 
>ever written. And I will tell you now, it was vastly ahead of 
>anything else at the time - and I include Unix. Obviously, everything 
>in the system was designed around writing in assembler, and hence 
>large applications (DTP, editors, spreadsheets, photo-editing, music 
>composition etc.) often were entirely written in hand-coded ARM. 
>Hence all of us did stuff which most people consider unlikely in 
>assembler - for example, we used what you could call an object in 
>that some code would have instance data and a ctor and destructor. We 
>had the equivalent of virtual functions using API offset tables. Some 
>silly people used self-modifying code, which is worse that goto's 
>IMHO.

:-)


>What is important to get from this is that until the US 
>multinationals crushed our indigenous European computer industry, we 
>were in many ways considerably ahead of the status quo. This is why I 
>don't fit easily into boxes others like to assign me to.
>
>>>>Ouch, that's certainly a very dubious design style. It's a typical 
>>>>hacker's style, and it comes from the Smalltalk world, but it's 
>>>>generally inappropriate for C++ or Java or any other statically 
>>>>typed OO language.
>>>
>>>Can you point me to resources explaining why this is bad and not just
>>>a question of individual style?
>>
>>Sure no problem. Start with our book ("C++ FAQs", Addison Wesley), 
>>then go to Scott Meyer's books ("Effective C++" and "More Effective
>>C++", also Addison Wesley), and probably most any other book that
>>deals with design/programming style in C++.
>
>Not being able to obtain these books easily (I live in Spain plus 
>money is somewhat tight right now), I looked around the web for more 
>on this. I specifically found what not to do when inheriting plus how 
>deep subclassing usually results in code coupling increasing. Is that 
>the general gist?

That's a start. But coupling between derived and base class is a
relatively smaller problem than what I'm talking about. Typically deep
hierarchies end up requiring a lot of dynamic type-checking, which boils
down to an expensive style of coding, e.g., "if the class of the object
is derived from X, down-cast to X& and call method f(); else if it's
derived from Y, down-cast to Y& and call g(); else if ...<etc>..." This
happens when new public methods get added in a derived class, which is
rather common in deep hierarchies. The if/else if/else if/else style of
programming kills the flexibility and extensibility we want to achieve,
since when someone creates a new derived class, they logically need to
go through all those if/else-if's and add another else-if. If they
forget one, the program goes into the "else" case, which is usually some
sort of an error message. I call that else-if-heimer's disease
(pronounced like "Alzheimer's" with emphasis on the word "forget").

Take this as an example. Suppose someone creates a HashTable class that
has methods insert(const Item&), contains(const Item&), remove(const
Item&), and size(). Suppose the author of HashTable prepared the class
to be a base class, e.g., virtual methods, protected data, etc.

Next someone comes along and wants to create class Bag (AKA MultiSet),
which is an unordered container into which you can insert more than one
copy of an Item. The methods of HashTable are almost perfect for Bag,
so they inherit Bag from HashTable. That solution makes a lot of sense
*if* you have the mindset that inheritance is for reuse, but it is
dubious (at best) if you take my approach.

So far we have Bag inheriting from HashTable, with minimal overrides.
Next someone comes along and wants to create Set, which semantically is
a "specialized Bag." Set is specialized in the sense that it contains
at most one copy of any Item. They draw a Venn diagram and prove that
*every* Set is already a Bag, so the "specialization" concept seems to
make a lot of sense, so they go ahead and inherit Set from Bag. This
again makes sense in an "inheritance is for reuse" mindset, but it is
increasingly dubious from my perspective. Note that to make the
"specialization" work, they override the insert(const Item&) method so
it makes sure the Set never gets duplicates.

Then someone comes along and wants to create a Dictionary (AKA Map)
class, which is conceptually a set-of-associations, that is, a
set-of-key/value-pairs. So they inherit Association from Item (after
all, they reason, an Association really is a special kind of Item), and
they inherit Dictionary from Set (after all, a Dictionary really is a
set-of-associations). They might add a method or two, e.g., to perform
the mapping from Key to Value, and they might provide insert(const
Association&) and perhaps even privatize insert(const Item&), just to
make sure no one accidentally inserts a non-Association Item into the
Dictionary. Again all this makes sense *if* you believe inheritance is
for reuse, and coincidentally you end up with a somewhat tall hierarchy,
but it's really strange and dubious from the perspective I'm espousing.

Now let's see how *each* of those three hierarchies will cause problems.

The problem with the first inheritance (Bag from HashTable) is
performance. For example, suppose we later discover that HashTable is
not the ideal data structure for constructing a Bag. If we had used
aggregation / has-a, we would have been able to change the internal data
structure to something else (e.g., skip-list, AVL tree, 2-3 tree,
red-black tree, etc., etc.) with almost zero ripple effect. (I use the
term "ripple effect" to describe the number of extra changes that are
needed. E.g., if we change this, we'll also have to change that; and if
we change that then we'll have to change this other thing. That's a
ripple-effect.)

However since we used inheritance, we're pretty much stuck with
HashTable forever. The reason is that inheritance is a very "public"
thing -- it tells the world what the derived class *is*. In particular,
users throughout our million-line-of-code system are passing Bags as
HashTables, e.g., converting a Bag* to HashTable* or Bag& to HashTable&.
All these conversions will break if we change the inheritance structure
of Bag, meaning the ripple effect is much higher.

The problem with the second inheritance (Set from Bag) is a logical
mistake, and sooner or later, it will cause the application to generate
errors. Even though a Venn diagram will prove that *every* Set actually
is a Bag, and even though conceptually Set is a "specialized" Bag, the
inheritance is improper.

A derived class's methods are allowed to weaken requirements
(preconditions) and/or strengthen promises (postconditions), but never
the other way around. In other words, you are free to override a method
from a base class provided your override requires no more and promises
no less than is required/promised by the method in the base class. If
an override logically strengthens a requirement/precondition, or if it
logically weakens a promise, it is "improper inheritance" and it will
cause problems. In particular, it will break user code, meaning it will
break some portion of our million-line app. Yuck.

The problem with Set inheriting from Bag is Set weakens the
postcondition/promise of insert(Item). Bag::insert() promises that
size() *will* increase (i.e., the Item *will* get inserted), but
Set::insert() promises something weaker: size() *might* increase,
depending on whether contains(Item) returns true or false. Remember:
it's perfectly normal and acceptable to weaken a
precondition/requirement, but it is dastardly evil to strengthen a
postcondition/promise.

To see how this weakening will break existing code, imagine that code
throughout our million-line system passes Set objects to functions
expecting Bag-references or Bag-pointers. Those other functions call
methods of the Set object, though the functions only know the interfaces
described by Bag. (Nothing unusual here; what I just described is the
very heart of dynamic binding.) Some of that code inserts 2 copies of a
particular Item, then removes it once, and divides something by
contains(Item) (knowing contains(Item) will be at least 1!), yet
contains(Item) will actually be 0!

Please don't assume the solution is to make insert(Item) non-virtual.
That would be jumping from the frying pan into the fire, since then
Bag::insert() would get called on a Set object, and there actually could
be 2 or 3 or more copies of the same Item inside a Set object!! No, the
real problem here isn't the override and it isn't the virtualness of the
method. The real problem here is that the *semantics* of Set are not
"substitutable for" those of Bag.

The solution here is (again) to use has-a rather than inheritance. Set
might have-a Bag, and Set::insert() would call its Bag's insert()
method, but would first check its Bag's contains() method. This would
be perfectly safe for everyone.

The third inheritance (Dictionary from Set) is another "improper
inheritance," though this time because it strengthens a
precondition/requirement. In particular, Set::insert() can accept any
Item but Dictionary::insert() has a stronger precondition: the parameter
must be a kind-of Association. The rules of C++ seem to help a little,
since Dictionary::insert(Association) doesn't override
Set::insert(Item), but that doesn't really solve anything since the
insert(Item) method is still accessible on a Dictionary object via a
Set& or Set*. Again making Set::insert() non-virtual will only make
things worse, since then that method will actually get called on a
Dictionary object provided it is called via a Set& or Set*, and that
method lets users insert *any* kind-of Item (not just Associations) into
the Dictionary. That insertion will undoubtedly cause a crash when an
Item is down-casted to a Association, e.g., to access the Association's
Key or Value.

As before, aggregation would be perfectly safe and reasonable here:
Dictionary could have-a Set, could insert Association objects (which
would automatically be up-casted to Item&), and when it accessed/removed
those Items, Dictionary could down-cast them back to Association&. The
latter down-cast is ugly, but at least it is logically safe --
Dictionary *knows* those Items actually are Associations, since no other
object anywhere can insert anything into the Set.

The message here is NOT that overrides are bad. The message here is
that tall hierarchies, particularly those built on the "inheritance is
for reuse" mantra, tend to result in improper inheritance, and improper
inheritance increases time, money, and risk, as well as (sometimes)
degrading performance.


>>>I would have thought it /better/ for
>>>statically typed languages because the compiler is given more 
>>>knowledge with which to optimise.
>>
>>Nope, it's a very Smalltalk-ish style, and it causes lots of problems 
>>in a statically typed OO language since today's statically typed OO 
>>languages (C++, Java, Eiffel, etc.) equate inheritance with subtyping.
>>In any language that equates inheritance with subtyping, using 
>>inheritance as a reuse mechanism, as opposed to using inheritance 
>>strictly for subtyping purposes, ultimately causes lots of design and 
>>extensibility problems. It can even effect performance.
>
>In other words, precisely the trap I was falling myself into. I 
>should however mention that having examined my code, I was peforming 
>this trap only in the areas where Qt wasn't providing what I needed. 
>In the code generated entirely by myself, I tend to use a top-down 
>approach with an abstract base class defining the reusable parts.

Good.

Remember, on a small enough project, you can use inheritance in pretty
much any way you want since you can easily fit the entire system into
your head at once. The problem is with larger systems, where very few
programmers can remember all the constraints. In large systems,
inheritance really has to mean subtyping, since then programmers can't
hurt themselves if they do what C++ is designed to do: pass a Derived
object via a Base& or Base*, then access methods on the Derived object
via those Base& or Base*.


>I should mention that much of the subclassing I have had to do will 
>disappear with future versions of Qt as they have very kindly mostly 
>agreed with my ideas. Hence, in fact, until v4.0, it's mostly stop- gap
code.
>
>>>Again, I'd like to know precisely why this style would be a poor 
>>>choice for some other app.
>>
>>Mostly because it creates all sorts of problems for users. Take, for 
>>example, your TSortedList class. You have removed the append() and
>>prepend() methods because you can't implement them properly in your 
>>class. Nonetheless someone might easily pass an object of your 
>>derived class via pointer or reference to its base class, and within 
>>that function the methods you tried to remove are suddenly available 
>>again, only this time with potentially disastrous results. Take, for 
>>example, this function:
>>
>> void f(QList<Foo>& x)
>> {
>> x.prepend(...); // change '...' to some Foo object
>> x.append(...); // change '...' to some Foo object
>> }
>>
>>Now suppose someone passes a TSortedList object to this function:
>>
>> void g()
>> {
>> TSortedList<Foo> x;
>> f(x);
>> ...what happens here??
>> }
>
>Err, prepend and append aren't virtual in the base class, so the base 
>class' versions would be called. 

As I pointed out below, that is worse than if they were virtual. In
particular, "the list might not be sorted" (see below) precisely because
f() could call x.prepend() and x.append() in random order. The
un-sorted-ness of the TSortedList could cause all sorts of problems in
the binary search routines. Try it and you'll see: if you have an
unsorted list and you apply binary search anyway, the results are random
at best.


>I had realised previously that it's 
>a very bad idea to disable virtual inherited methods - or if you were 
>to, you'd want a fatal exception in there to trap during debug.

Actually doing it with a virtual is about the only time you have a
prayer of being right. But even then, all the above options are bad if
the new behavior is disallowed by the "contract" in the base class (the
"contract" is the explicit preconditions and postconditions):

* If the base class's method, say Base::f(), says it might throw a Foo
or Bar exception, then an override is allowed to throw a Foo or Bar or
any class that inherits from either of those, BUT NOTHING ELSE.

* If Base::f() says it never throws an exception, the derived class must
never throw any exception of any type.

* If Base::f() says it might "do nothing," then overriding the behavior
with "Derived::f() { }" is perfectly legitimate. However if Base::f()
guarantees it always does something, then "{ }" in the derived class is
bad - a weakened promise.

However redefining ("overriding") a non-virtual is almost always bad,
since then the behavior of the object would depend on the type of the
pointer instead of simply on the type of the object. The goal for
objects to respond based on what they really are independent of who is
looking at them. In the real world, if I am pointing at a Mercedes
ES-500 but I simply call it a Car, it still drives the same way -- the
behavior of the object doesn't depend on the type of the pointer.


>>In the '...what happens here??' part, anything you do to the 
>>TSortedList is likely to cause problems since the list might not be 
>>sorted. E.g., if f() adds Foo objects to 'x' in some order other than
>>the sorted order, then the '...what happens here??' part is likely to 
>>cause serious problems.
>>
>>You can't blame this problem on references, since the same exact thing
>>would happen if you changed pass-by-reference to pass-by-pointer.
>>
>>You can't blame this problem on the C++ compiler, because it can't 
>>possibly detect one of these errors, particularly when the functions
>>f() and g() were part of two different .cpp files ("compilation
>>units") that were compiled on different days of the week.
>>
>>You can't blame this problem on the author of g(), because he believed
>>the contract of TSortedList. In particular, he believed a TSortedList
>>was a kind-of a QList. After all that is the meaning of subtyping, 
>>and subtyping is equated in C++ with inheritance. The author of g() 
>>simply believed what you said in this line: 'class TSortedList : 
>>public QList', and you can't blame him for believing what you said.
>>
>>You can't blame this problem on the author of f(), because he believed
>>the contract of QList. In particular, he believed he can append() 
>>and/or prepend() values in any order onto any QList. Besides, he 
>>wrote and compiled his code long before you even thought of deriving 
>>TSortedList, and by the rules of extensibility (e.g., see the sections
>>on Inheritance in the C++ FAQ, or any similar chapters in any book on 
>>the subject), he is not required to predict the future - he is 
>>supposed to be able to write code based on today's realities, and have
>>tomorrow's subclasses obey today's realities. That is the notion of 
>>is-a, and is codified in many places, including the C++ FAQ, Liskov's 
>>Substitutability Principle ("LSP"), and many other places.
>>
>>So who is at fault? Ans: the author of TSortedList. Why is the 
>>author of TSortedList at fault? Because of false advertising: he said
>>TSortedList was a kind-of a QList (or, using precise terminology, that
>>TSortedList was substitutable for QList), but in the end he violated 
>>that substitutability by removing methods that were promised by QList.
>
>Worse I think would be redefining inherited methods to do something 
>completely different. But yes, I understand now.
>
>The rule should be that subclasses must always behave like their base 
>class(es). 

Right.

>Code reuse should be done via composition.

Right.

>Hence, that TSortedList should now derive off QGList which doesn't 
>have the append and prepend methods so I can safely ensure it does 
>what its parent does.

What you really ought to do is check the *semantics* of QGList's
methods, in particular, read the preconditions and postconditions for
those methods. (I put these in the .h file for easy access, then use a
tool to copy them into HTML files; Qt seems to put them in separate
documentation files; either way is fine as long as they exist
somewhere.) Inheritance is an option if and only if *every* method of
TSortedList can abide by the corresponding preconditions and
postconditions in QGList.

Again, in a small enough project, you can use "improper inheritance" if
you want, but you must be very sure that no one ever uses a Base& or
Base* to point to a Derived object. (Personally I never use improper
inheritance, since the down-side cost is unlimited. In contrast, most
"bad programming practices" have a bounded cost, e.g., a "goto" might
increase the maintenance cost of its method, but it can never screw up
any other method, so its cost is bounded by the number of lines in the
method. However the down-side cost for improper inheritance goes on and
on: the more users use your code, the more places that can break.)


>>Put it this way: you inherit from "it" to *be* what it *is*, not 
>>simply to have what it has. If you simply want to have what it has, 
>>use has-a (AKA aggregation AKA composition).
>
>Yes, I definitely understand you now. It is a pity explanations like 
>this weren't more universally available, 

Glad I could help.

>because I know a lot of C++ 
>programmers learned from MSVC's online help (eg; initially me - it's 
>where I learned C from as well). I however did subscribe to the 
>Association of C & C++ Users which is why I know about 
>standardisation debates - but even though at the time I subscribed 
>for the C coverage, I did read the C++ sections.
>
>A lot of people say pointers are the devil's tool in C and I have met 
>a disturbing number of programmers who just don't understand them. 
>However, it seems to me pointers are child's play in unenforced 
>danger when compared to problems like you and your FAQ have 
>mentioned. 

Agreed. The reason improper inheritance has larger consequences than,
say, pointers is that improper inheritance is a *design* error, but
pointer problems are "merely" programming errors. (I recognize I'm
using the word "design" differently from you. Play along with my lingo
for a moment and pretend your inheritance hierarchies really are part of
your design.) Obviously the cost of fixing a design error are greater
than the cost of fixing a programming error, and that is my way of
explaining your (correct) observation above.


>If more warning were out there, we'd all have less 
>problems with other people's code.

Agreed. I get on my stump and shake my fist in the air every chance I
get. You are hereby deputized to do the same. Go get 'em!!

Seriously, proper use of inheritance really is important, and knowing
the difference is critical to success in OO, especially in large
systems.


>>(BTW I will quickly add that your approach is perfectly fine in a very
>>small project, since in very small projects you can control the damage
>>of "improper" or "bad" inheritance. Some of my colleagues won't agree
>>and will say your approach is *always* wrong, and in a sense I would 
>>agree. But from a practical basis, your approach doesn't really cost 
>>too much in the way of time, money, or risk with a small enough 
>>project. If you use your approach on a big project, however, everyone 
>>seems to agree, and everyone's experience seems to prove, that your 
>>approach is very dangerous and expensive.)
>
>Unfortunately what I am working on now I expect to exceed 100,000 
>lines before I'll consider it reasonably done. I'll explain later.
>
>>[content clipped]
>>**DING** I just found your bug. In TSortedList.cpp, all your methods
>>are listed like this: [code chopped]
>>Your syntax ("TSortedList<class type>::") explains everything,
>>including the bizarre use of 'class type' within the error messages. 
>>What happened is that the compiler saw you using a TSortedList<class
>>type>, and it therefore tried to compile all the virtual methods
>>within TSortedList<class type>. When it saw that 'type' really isn't 
>>a genuine class type, it complained (eventually) that the class called
>>'type' doesn't have an == or < operator.
>
>I don't see how the syntax is consistent then.

Just trust me and type it in. If the compiler gives you errors, then
tell me and I'll help you debug it.

>From what I can see 
>template<pars> X where X is the code to be parametrised - or are you 
>saying I declare the methods in the class definition and move the 
>code to inline void TSortedList<class type>::foo() after the template 
>class definition?

No, just change
void TSortedList<class type>::f() { ... }
to
template<class type> TSortedList::f() { ... }


>Either way, this was my first ever template class (yes, in all three 
>years of using C++) and I copied heavily off QSortedList.h (which I 
>enclosed last time) which might I point out compiles absolutely fine. 

I'm sure (without having seen it!) that QSortedList uses the syntax I
described above. Either that or they moved all their method definitions
into the body of the class.


>So why my class, almost identical, does not and Qt's one does I do 
>not know.

Trust me: change "void TSortedList<class type>::f() { ... }" to
"template<class type> TSortedList::f() { ... }".


>>When you fix this problem, you will end up with additional problems, 
>>mainly because you have moved template code into a .cpp file. The C++
>>FAQ covers this issue; suggest you read that for details. (It's 
>>probably in the section on templates and/or containers.)
>
>It says you can't move template code into a .cpp file :)

Oops, I thought I gave the other ideas. It's really simple: if you want
to create TSortedList's of Foo, Bar, and Baz, just add these lines at
the bottom of TSortedList.cpp:

template class TSortedList<Foo>;
template class TSortedList<Bar>;
template class TSortedList<Baz>;

Or, if you'd rather not change TSortedList.cpp itself (e.g., if you want
to reuse without changes that on other projects), then create
MyProjectTSortedList.cpp which says

#include "TSortedList.cpp" // note: not ".h"!!!
#include "Foo.hpp"
#include "Bar.hpp"
#include "Baz.hpp"

template class TSortedList<Foo>;
template class TSortedList<Bar>;
template class TSortedList<Baz>;

Note: the first line includes a .cpp file, not a .h file!!

The point is that MyProjectTSortedList.cpp would be compiled as part of
your project (e.g., in your makefile), but TSortedList.cpp would not.


>I think you can just put it in the header file though? I'm still not 
>sure why it threw an error :(

It's because the syntax you used ("void TSortedList<class type>::f() {
... }") is **NOT** the C++ way of defining a template member function.
That is the C++ way of saying you want to specialize the particular
template "TSortedList<type>", meaning the name 'type' will be
interpreted as a real typename as opposed to a formal parameter for the
template. In other words, when the compiler saw the syntax you used, it
tried to actually look for a type named 'type', and it tried to create
'TSortedList<type>'. When it did that, it noticed it needed to create
TSortedList<type>::compareItems() (again where 'type' here means a real
type name, not a formal parameter to the template), so it went through
the code and noticed you were casting a 'void*' to a 'type*' (which it
can do even if it doesn't know anything about the type 'type'), then you
dereferenced that 'type*' (which means the type of the thing is now
'type&'; again this is a legal type even if the compiler doesn't know
anything about the type named 'type'), then you used '==' or '<' to
compare two 'type' objects. At this point the compiler *should* have
said 'type' was incomplete, since that would have given you a better
hint, but instead it said 'type' doesn't have an '==' operator.

Is that more clear?


>>>[a bloody good suggestion]
>>>Thank you!
>>
>>No problem. BTW I consider this an idiom of C++. Part of being 
>>competent in using a language is knowing the syntax and semantics, but
>>another critical part is knowing the idioms of the language. You're 
>>an expert (I assume) in certain varieties of assembler, and perhaps 
>>also in C. As a competent C programmer, you know the C idioms, such 
>>as
>>
>> while (*dest++ = *src++)
>> ;
>>
>>This, of course, is the idiom that copies an array of things pointed 
>>to by 'src' into an array pointed to by 'dest', and it stops copying 
>>after it copies the item whose value is zero. If the arrays are 
>>arrays of 'char', this is equivalent to strcpy(), since it copies 
>>everything including the terminating '\0'. Obviously other types are 
>>similar.
>
>That kind of dangerous code brought out a compiler bug in a version 
>of GCC and MSVC 5 if I remember correctly. The increments weren't 
>always done with the load and store when full optimisation was on. 
>Solution: use comma operator.

I'll take your word for it, and I really don't want to argue over it,
but I'm *very* surprised that any C compiler ever shipped any version
that couldn't correctly compile the above. That snippet is an idiom
that is used in many, many C programs, and is probably part of the
compiler's test suite.


>>Other idioms in C abound, such as Duff's device:
>>
>> while (n >= 0) {
>> switch (n) {
>> default: xyzzy;
>> case 7: xyzzy;
>> case 6: xyzzy;
>> case 5: xyzzy;
>> case 4: xyzzy;
>> case 3: xyzzy;
>> case 2: xyzzy;
>> case 1: xyzzy;
>> }
>> n -= 8;
>> }
>>
>>If you replace 'xyzzy' with some piece of code, this applies that 
>>piece of code exactly 'n' times, but it is much faster than the
>>equivalent:
>>
>> while (n-- > 0) {
>> xyzzy;
>> }
>>
>>Since the latter executes 'n' decrements, 'n' comparisons, and 'n' 
>>conditional jumps, whereas Duff's device executes only 1/8'th as many 
>>decrements, comparisons, or conditional jumps. Of course the key is 
>>that there is no 'break' statement after each 'case' -- each case 
>>"falls through" to the next case. The other key, obviously, is that 
>>the cases are listed in backwards order.
>
>This is called loop unrolling in assembler and I thought compilers 
>did it for you because modern processors run so much faster than 
>system memory that the compsci measurement of execution time is often 
>way way off - smaller code on modern processors goes faster than 
>bigger code, even with loads of pipeline flushes from the conditional 
>branches because the L1 cache is 10x system memory speed.

I suppose some optimizers might unroll some loops, but the real problem,
as you obviously know, is cache misses. Plus compilers can't
necessarily know that any given loop will actually be a bottleneck, and
as you know, performing this sort of optimization on a non-bottleneck
loop would make space-cost worse without any improvement in overall
time-cost. If a program has 1,000 loops, how could a compiler guess
which of those are the bottlenecks? As has been demonstrated by
software engineering study after study over the years, programmers don't
even know where their own bottlenecks are, so I expect it will be a long
time before compilers can know.


>>The point, of course, is that this is another idiom of C, and 
>>competent C programmers know these sorts of things. As you become 
>>better and better at C++, you will learn the idioms of C++, and this 
>>is one of them.
>
>Actually, using a switch() statement is bad on modern deep pipeline 
>processors. It's better to use a function pointer table and calculate 
>the index because then the data cache effectively maintains a branch 
>history for you.
>
>If you ask me about embedded systems, I don't doubt I'm as good as 
>they get.

Sounds like it. I should remember that in case I get more embedded
systems work from TI or UPS or HP. In the mean time, learn proper
inheritance so you'll be ready for my phone call! :-)

(Don't sit by the phone waiting for it to ring - I don't have anything
'hot' right now.)

>All this high-level stuff though I must admit is beyond me 
>a bit. 

As you know, embedded systems programming is highly technical, and
presents enough of a challenge that the weak tend to get killed off -
they end up programming way up at the apps level using something soft
and gushy like Visual Basic or JavaScript. So the only survivors in
embedded systems are technically tough enough, at least at the
programming level.

Unfortunately most embedded systems programming doesn't also force
people to be really good at the design level. Most embedded systems
work is intense at the binary-level, always trying to squeeze 10
kilograms of stuff in a bag meant to hold only 5 kilograms. I think
that's especially true in the hand-held environment, but either world
tends to produce hot-shot programmers who can program their way out of
most situations, and aren't necessarily great at the high-level stuff.
But you'll make it - I can tell. You've already embraced the key
elements of good OO design (except for understanding that OO design
really means the structure of your inheritance relationships, and that
your algorithms are pluggable/replaceable and end up getting buried in
derived classes; more on that later).

>But more on that later.
>
>>>I have the utmost respect and admiration
>>>for any standardisation committee (with possible exception of the
>>>POSIX threads committee, their poor design really screws C++ stack
>>>unwinding which is unforgiveable given how recently it was designed).
>>
>>Not knowing any better, I'd guess they were dealing with very subtle 
>>constraints of existing code or existing practice.
>
>Twas a completely new API AFAIK.

Sigh. That's very sad. Oh well, I gave them the benefit of the doubt,
but apparently they weren't worthy of it.

>>Most people on
>>those sorts of committees are competent, plus the entire world
>>(literally) has a chance to comment on the spec before it gets 
>>finalized, so if there were gaping holes *that* *could* *be* *fixed* 
>>(e.g., without breaking oodles and oodles of existing code), I'm sure
>>*someone* *somewhere* in the world would have pointed it out.
>
>From the usenet posts I've read, people did point out POSIX thread 
>cancellation did not offer C++ an opportunity to unwind the stack, 
>but they ignored it and went with a setjump/longjmp solution instead. 
>Now if your platform's setjmp implementation unwinds the stack - 
>fantastic. If not, severe memory leakage. All they needed was the 
>ability to set a function to call to perform the cancellation - under 
>C++, that would be best done by throwing an exception. Still, we can
>hope it will be added in the near future.
>
>>Like I said, I don't know the details of this particular situation,
>>but I would guess that they were fully aware of the problem, that they
>>investigated all the alternatives, and that they chose the "least bad"
>>of the alternatives.
>
>The above support for C++ (and other languages) costs maybe an hour 
>to add and is completely portable. I can't see how it wasn't done by 
>competent and fair designers. I have read rabid posts on usenet why 
>you shouldn't be writing mulithreaded C++ and such bollocks - not 
>sure if that's involved. The whole issues of threads gets many Unix 
>people's knickers in a right twist - they seem to think they're 
>"wrong", much like exceptions are "wrong" in C++ for some people. 
>Weird.
>
>>Yes, existing code puts everyone in a very difficult position, and
>>often causes compromises. But that's the nature of the beast. The
>>cost of breaking existing code is much, much greater than canonizing
>>it. [clipped rest]
>
>Have you noticed the world's most popular programming languages tend 
>to be evolved rather than designed? ;)

Yes, and I think there's a reason. You might not like my reasoning, but
it goes like this: Businesses choose programming languages based on
business issues first and technology issues second. This is not a bad
thing. In fact, I believe businesses *ought* to worry primarily about
business issues such as acquiring skilled people and tools. Can we hire
programmers that already know this language? Are there a glut of
programmers, or are we going to have to pay enormous salaries, signing
bonuses, and relocation fees? Are the area universities churning out
bodies that know this language? Are the programmers any good? Is there
a ready supply of programmers we can "rent" (AKA contractors) so we
don't have to hire everyone? Are there outsourcing firms we can bring
in to finish the work as a contingency plan? Is there a ready supply of
consultants who can advise us on nuances of using this language? Those
are examples of the people-questions; next come a similar pile of
questions about tools, multiple vendors, long-term support, development
environments, maturity of tools, companies who can train our people in
using the tools, etc., etc.

And after all these questions are answered, somewhere down on the list
are things like the relative "cleanliness" of the language. Are the
constructs orthogonal? Is there appropriate symmetry? Are there
kludges in the syntax? Those things will effect the cost of the
software some, to be sure, but they aren't life-and-death issues like
whether we can buy/rent programmers or whether we can buy/license good
tools. I have a client that is using (foolishly) a really clean,
elegant language that almost nobody uses. Most programmers who use that
language for more than a month absolutely love it. But the client can't
buy or rent programmers or tools to save its life, and its multi-million
dollar project is in jeopardy as a result.

So far all I've said is that most businesses choose programming
languages based primarily on business considerations, not primarily on
technical considerations. There are some exceptions (such as the
company I just mentioned), and perhaps you even experienced one or two
exceptions, but I think almost anyone would agree that the basic premise
("most businesses choose...") is correct. I further assert that that is
a good thing, and you are free to disagree on that point, of course.
However I have to believe you agree with me regarding how things *are*,
even if you disagree with me about how things *ought* to be.

The conclusion of the argument is simple: Go back through the
business-level questions I mentioned above, and most if not all of the
answers would be "okay" if the language was an incremental extension of
some well-known, mature language. That means using an "evolved"
language lowers business risk, even if it adds technical warts or
reduces technical elegance. (At least it's *perceived* to lower
business risk, but business people make decisions based on perception
rather than reality anyway, so the perception of a reduced business risk
is a powerful argument in favor of an "evolved" language.)


>>>Put it this way: when you try something which seems logical in C it
>>>generally works the way you think it should. 
>>
>>Really? No less a light as Dennis Ritchie bemoans the precedence of
>>some of the operators, and certainly the rather bizarre use of
>>'static' has caused more than one C programmer to wonder what's going
>>on. Plus the issue of order of evaluation, or aliasing, or any number
>>of other things has caused lots of consternation.
>
>Yeah, I've read his comments. There's one operator in particular - is 
>it &&? - which is very suspect in precedence.
>
>However, that said, once you get used to the way of C logic it stays 
>remarkably consistent. I personally recommend sticking "static" 
>before everything you want to be static and don't rely on default 
>behaviour - people's confusion with static clears up remarkably 
>quickly if you do that.

Yes, C is closer to the machine, since its mantra is "no hidden
mechanism." C++ *strongly* rejects the no-hidden-mechanism mantra,
since its goal is ultimately to hide mechanism - to let the programmer
program in the language of the *problem* rather than in the language of
the *machine*. The C++ mantra is "pay for it if and only if you use
it." This means that C++ code can be just as efficient as C code,
though that is sometimes a challenge, but it also means that C++ code
can be written and understood at a higher level than C code -- C++ code
can be more expressive -- you can get more done with less effort. Of
course it is very hard to achieve *both* those benefits (more done with
less effort, just as efficient as C) in the same piece of code, but they
are generally achievable given a shift in emphasis from programming to
design (using my lingo for "design"). In other words, OO software
should usually put proportionally more effort into design than non-OO
software, and should have a corresponding reduction in the coding
effort. If you're careful, you can have dramatic improvements in
long-term costs, yet keep the short-term costs the same or better as
non-OO.

People who don't understand good OO design (my definition, again; sorry)
tend to screw things up worse with OO than with non-OO, since at least
with non-OO they don't *try* to achieve so many things at once -- they
just try to get the thing running correctly and efficiently with
hopefully a low maintenance cost. In OO, they try to use OO design (my
defn) in an effort to achieve all those *plus* new constraints, such as
a dramatic increase in software stability, a dramatic reduction in
long-term costs, etc. But unfortunately, after they spend more
time/money on design, they have a mediocre design at best, and that
mediocre design means they *also* have to pay at least as much
time/money on the coding stage. They end up with the worst of both
worlds. Yuck.

The difference, of course, is how good they are at OO design (using my
defn).


>>But I guess I agree to this extent: C++ is larger than C, and as such
>>C++ has more confusing issues. I believe that C99 is causing some of
>>those same problems, however, since C99 is much bigger than its
>>predecessor. The same thing will be true of C++0x: it will be bigger
>>and have more compromises.
>
>It's also a case of history. K&R C was 90% done by just a few guys. 
>C++ is a collection of different enhancements over C by completely 
>different people with different intentions, and then with a good 
>dollop of academic theory thrown in for good measure. Hence its non-
>uniformity and lack of consistency.

In his first presentation to the ANSI/ISO C++ committees, Bjarne
Stroustrup made a statement that would guide the rest of the committee's
work: "C++ is not D." The idea was that we were *not* free to break C
compatibility unless there was a compelling reason. So in addition to
everything you said above, C++ ends up trying to be a better C. It
basically wants to be successful at 3 distinct programming paradigms:
procedural, object-oriented, and generic. The crazy thing is, if you're
willing to put up with some little edge effects that were designed for
one of the other paradigms, it actually succeeds at all three!


>Note that I have not yet found an academic who thinks C is an 
>excellent example of a procedural language :)
>
>>>TQString foo;
>>>foo="Hello world";
>>>
>>>Now TQString is a subclass of QString, and both have const char *
>>>ctors. The compiler will refuse to compile the above code because
>>>there are two methods of resolving it. 
>>
>>I may not understand what you mean by "two methods of resolving it,"
>>but I don't understand why the compiler doesn't do what you think it
>>should above. If TQString has a const char* ctor, then I think that
>>should promote "Hello world" to a TQString and then use TQString's
>>assignment operator to change 'foo'.
>
>I completely agree. However, MSVC wants you to put a TQString("Hello 
>world") around every const char * :(

It shouldn't. Try this code and see if it causes any errors:

=================================================
class BaseString {
public:
BaseString(const char* s);
};

class DerivedString : public BaseString {
public:
DerivedString(const char* s);
};

int main()
{
DerivedString foo;
foo = "Hello world";
return 0;
}
=================================================

I think that properly represents the problem as you stated it:
>>>TQString foo;
>>>foo="Hello world";
>>>
>>>Now TQString is a subclass of QString, and both have const char *
>>>ctors. The compiler will refuse to compile the above code because
>>>there are two methods of resolving it. "

Let me know if the above compiles correctly. (It won't link, of course,
without an appropriate definition for the various ctors, but it ought to
compile as-is.)

If the above *does* compile as-is, let's try to figure out why you were
frustrated with the behavior of TQString.


>I'm running into similar problems with the << and >> operators - I've 
>subclassed QDataStream with TQDataStream because QDataStream is 
>default big endian and doesn't provide support for 64 bit integers. 

Again, I'd suggest trying something different than subclassing
QDataStream, but I don't know enough to know exactly what that should
be.


>Every single time I use << or >> I'm an ambiguous resoluton error 
>when clearly the source or destination object is a TQDataStream.
>
>Most of my C++ problems are arising from "repairing" Trolltech's 
>code. 

And, I would suggest, mostly because you are repairing it via
inheritance.


>While they will fix things similarly in future Qt's as a result 
>of my suggestions, it still leaves now.
>
>>> The same sort of
>>>thing applies to overloading functions - you cannot overload based on
>>> return type, something I find particularly annoying.
>>
>>Another C++ idiom lets you do just that. I'll have to show that one
>>to you when I have more time. Ask if you're interested.
>
>Is that like this:
>bool node(TQString &dest, u32 idx)
>bool node(TKNamespaceNodeRef &ref, u32 idx)
>...

Nope, I'm talking about actually calling different functions for the
following cases:

int i = foo(...);
char c = foo(...);
float f = foo(...);
double d = foo(...);
String s = foo(...);


>>>Computer history is strewn with cases of an inferior product 
>>>destroying a superior product. It's hardly unique.
>>
>>I agree. I guess my point is simply this: any popular language is
>>going to have warts that an unpopular language will not. Take Eiffel
>>for example. Way back when Eiffel was very young, Bertrand Meyer
>>derided C++'s 'friend' construct, claiming it violated encapsulation. 
>>Then he began to get real users who were building real systems using
>>Eiffel, and suddenly he began to see how something like the 'friend'
>>construct actually *improves* encapsulation. So he added it to
>>Eiffel. At first the language seemed cleaner and simpler, then
>>gradually it added more stuff as it became more practical.
>
>Still, Eiffel is considered much cleaner than C++ - however, it's not 
>as popular. cf. my statement above about popular languages not being 
>designed.

Eiffel was considered high-risk. The low-risk alternatives were
Objective-C and C++. Objective-C was higher risk because the OO part of
it was completely separated from the non-OO part -- they weren't
integrated into a seamless whole, and that meant you couldn't easily
"slide" back and forth between OO and non-OO. Naturally technical
people are a little concerned about the ability to "slide" back and
forth between OO and non-OO, but from a business standpoint, that
ability reduces risk -- it gives us the contingency plan of
buying/renting C programmers/tools/etc.


>>However if you
>>pointed out any particular compromise, I could probably tell you why
>>it was done and in fact could (I hope!) make you realize that
>>"cleaning up" that compromise would cause more harm than good.
>
>Ok:
>1. Why didn't C++ have separated support for code reuse and subtyping 
>(like Smalltalk)?

Because C++ is statically typed. The thing that lets Smalltalk separate
the two is that Smalltalk attaches *no* semantic meaning to inheritance.
For example, if you have a Smalltalk function that calls methods
'push()' and 'pop()' on an object passed in as a parameter, you might
call that parameter 'aStack'. But there is *no* requirement that you
pass an object of a class that actually inherits from Stack. In fact,
if I create a class Employee that doesn't inherit from Stack but happens
to have 'push()' and 'pop()' methods (pretend the boss 'pushes' an
employee to make him work harder and 'pops' an employee when he makes a
mistake), then I could pass an object of Employee to your function, and
the Smalltalk runtime system would be perfectly happy: as long as my
object actually has methods called 'push' and 'pop', the runtime system
won't object.

The previous example showed how inheritance is not required for is-a,
and in fact is-a is determined solely based on the methods an object
happens to have. This next example shows how the compiler *never*
complains about the type of a parameter. Suppose you have a function
that calls method 'f()' and 'g()' on its parameter-object, but suppose
for some reason it only calls 'g()' on Tuesdays. Now suppose I create a
class that has a method f() but doesn't have a method g(). I can pass
an object of that class to your function, and provided I don't happen to
pass it on a Tuesday, everything should be fine. Note that I can
inherit from anything I want, and what I inherit from has nothing to do
with the type of parameter you take, since all (*all*, **all**,
***all***, ****ALL****) parameters are of type 'Object', and
*everything* (including an instance of a class, a class itself, an
integer, everything) is an object.

Because Smalltalk has *no* compile-time type-safety (e.g., if you try to
add two things, the compiler never knows if they're actually addable,
and only at run-time is the addition known to be safe or unsafe),
Smalltalk lets you inherit anything from anything in a random fashion.
You *can* equate inheritance with subtyping if you want, but you don't
have to, and certainly most "interesting" Smalltalk programs do not.
You can even (effectively) remove a method that was inherited from a
base class. You can even have an object change its class on-the-fly.
It's not as unconstrained as self-modifying code, but it's very, very
flexible.

I liken the difference to the difference between an all-terrain-vehicle
(guys love these in the USA; they drive through streams and over
mountains, the works) and a Ferrari. The Ferrari will get you there a
lot faster, but you *must* stay on the road. In C++ (and any other
statically typed OO language) you *must* stay on the road -- you must
restrict your use of inheritance to that which is logically equivalent
to subtyping. In Smalltalk, there is no restriction to staying on the
road, and in fact it's a lot more fun if you drive through your
neighbor's lawn, through the rose bush, through the flower bed, etc.,
etc. Of course you might get stuck in a rut on-the-fly, and of course
there are no guarantees you'll actually get to where you're wanting to
go, but it's a lot more fun trying!

So if C++ wanted to be like Smalltalk, it could do what you want. But
given that C++ wants compile-time type-safety, it can't do what you
want.


>2. Why don't return types determine overload?

Because things like this would be ambiguous:

int f();
float f();
char f();

int main()
{
f();
...
}

Worse, if the three 'f()' functions were compiled in different
compilation units on different days of the week, the compiler might not
even know about the overloads and it not notice that the call is
ambiguous.

There's an interesting example in Bjarne's "Design and Evolution of C++"
that shows how type safety would commonly be compromised if C++ did what
you want. Suggest you get that book and read it -- your respect for the
language and its (seemingly random) decisions will go up a few notches.


>3. Why can't the compiler derive non-direct copy construction? eg;
>class A { A(B &); } class B { B(C &}; } class C { C(const char *); }
>A foo="Hello";

This was done to eliminate programming errors. The problem is to avoid
surprising the programmer with bizarre chains of conversions that no
human would ever think of on his own. For example, if someone
accidentally typed this code:

A x = cout;

and if your rule was allowed, that might actually compile to something
really bizarre such as constructing an iostream from the ostream, then
constructing an iostream::buffer from the iostream, then constructing a
std::string from the iostream::buffer, then constructing a Foo from the
std::string, then calling the 'operator const char*()' method on that
Foo, then constructing a C from that 'const char*', then constructing a
B from that C, then constructing the A from the B.

No programmer would think that is intuitively obvious. Put it this way:
most programmers find the automatic conversion/promotion "magical," and
are somewhat afraid of them as a result. The idea of limiting the
number of levels is to put a boundary on how much magic is allowed. We
don't mind hiding mechanism from the C++ programmer, but we don't want
to hide so much mechanism that no programmer could ever figure out
what's going on.

Code must be predictable, therefore magic must be limited.


>In C++, you must rewrite that as A foo(B(C("Hello"))); - it's not 
>done for you, nor is there any way of fixing it except modifying A to 
>have a copy constructor taking const char * - which isn't possible if 
>you don't have the source to A or B.
>
>>Do you really mean "functional" or "procedural" here? The Functional
>>style is rather difficult to do in C++ (think Scheme). Functional
>>programming means never allowing any changes to any piece of data, so
>>instead of inserting something into a linked list, one creates a new
>>linked list and returns the new linked list that contains the new
>>item.
>
>I mean "functional" in terms of I tell you what to do not how to do 
>it. 
>
>Also above, the new linked list isn't created, merely a potential for 
>a separate new linked list is. You're right that it's "as if".
>
>>>>Another really big error. OO is primarily a design approach. The
>>>>concept of "OO programming" is very close to a misnomer, since OO
>>>>programming cannot stand on its own - it needs OO *design*.
>>>
>>>No, I must disagree with you there: design is independent of 
>>>language. 
>>
>>Nope, not true at all. A design that works for Functional languages
>>is horrible for Procedural languages, and vice versa. And both those
>>designs are wholly inappropriate for OO languages, Logic-oriented
>>languages, or Constraint-oriented languages. In short, the paradigm
>>*very* much effects the design.
>
>I would have said it affects the /implementation/ rather than the 
>design. You're right that say doing a sort in Haskell is completely 
>different than doing it in C - but I would call that a difference in 
>implementation because (a) the algorithm used is identical and (b) 
>the two solutions give identical output.
>
>>Try your belief out sometime. Try implementing your favorite program
>>in Prolog (logic-oriented) or Scheme (function-oriented) and see what
>>happens. Guaranteed that if your program is nontrivial, a radically
>>different design will emerge. Either that or you'll constantly be
>>fighting with the paradigm and the underlying language, trying to
>>force, for example, Prolog to be procedural.
>
>No, a radically different /implementation/ will emerge. That's simply 
>because implementing the design is best done one way in one language 
>and differently in a different language.
>
>>I'll go further: design isn't even independent of language *within* a
>>paradigm. In other words, a design that is appropriate for Smalltalk
>>is typically inappropriate for C++, even when you are trying very hard
>>to use OO thinking throughout.
>
>I'm getting a feeling that once again it's a disagreement about 
>terminology rather than opinion. I would treat the word "design" as 
>that in its purest sense - algorithms. Everything after that has 
>increasing amounts of implementation - so, for example, the object 
>structure would involve some amount of implementation detail.

Agree that this is mainly a difference in terminology.

HOWEVER there is an important conceptual difference as well. (I'll use
the term "software development" to avoid using either term "design" or
"implementation.")

In OO software development, the inheritance hierarchies are more
fundamental and more foundational than the algorithms or data
structures. That may seem strange to you, but if so, it's mainly
because of the *way* you've tended to use inheritance in the past.

Here's why I say that: start with an OO system that has a base class
'Foo'. Base class Foo is abstract. In fact, it is PURE abstract: it
has no data structures and algorithms -- it is a pure "interface" -- all
its methods are pure virtual. Next we create, over time, 20 different
derived classes, each of which has a different data structure and
algorithm. The methods have a similar purpose, since their contracts
are similar, but there is *zero* code reuse between these derived
classes since all 20 inherit directly from 'Foo'.

So the algorithms and data structures are wholly replaceable. In fact,
we intend to use all 20 different data structures and algorithms in the
same program at the same time (not implying threading issues here; "same
time" simply means "in the same "run" of the program, all 20 classes are
used more-or-less continuously).

In OO systems that smell even remotely like this, the core algorithms
and data structures are very secondary to the design, and in fact can be
ignored during early design. During late design, someone will need to
carefully select the best algorithms and data structures, but during
early design all that matters is the inheritance structure, the method
signatures in the base class 'Foo', and the "contracts" for those
methods. If the contracts are set up right, then all 20 derived classes
will be able to require-no-more, promise-no-less (AKA "proper
inheritance"), and the derived classes can totally bury the algorithms
and data structures.

It's almost like specifying an API then later implementing it. When
you're specifying the API, all you care about is that the parameters and
specifications ("contracts") are correct, complete, and consistent. If
your API is clean enough, you actually *want* to be able to ignore the
specific algorithm / data structure that will be used behind that API,
since if you can bury that information behind the API, you know the
algorithm / data structure can be scooped out and replaced if someone
later comes up with a better one. The difference is that with OO, we
have an API that has 20 different implementations and they're all
pluggable, meaning the guy who is using the API never knows which
implementation he's working with. That forces us to "do the right
thing" by designing our API (i.e., methods in 'Foo', parameters, and
especially contracts) in a way that all 20 derived classes are "proper"
and that none of the 20 "leak" any of their private info (algorithms and
data structures) to the user.

If you're still with me, then inheritance is more foundational than
algorithms and/or data structures. Your past style of inheritance
equated inheritance with data structure, after all, inheritance was just
a way to group two chunks of software together. But now that you see
the above dynamic-binding-intensive approach, perhaps you see that
inheritance is an earlier lifecycle decision than algorithm. That's why
I call the inheritance graph a critical (*the* critical) part of design.
Get that right and the rest is replaceable.


>>>I have never agreed with OO design as my university 
>>>lecturers found out - I quite simply think it's wrong. Computers
>>>don't work naturally with objects - it's an ill-fit.
>>>
>>>What computers do do is work with data. If you base your design
>>>entirely around data, you produce far superior programs. 
>>
>>In your experience, this may be true. But trust me: it's a big world
>>out there, and in the *vast* majority of that world, your view is very
>>dangerous.
>
>I have applied my skills to many projects: public, private and 
>personal and I have not found my data-centric approach to have failed 
>yet. It has nothing to do with code maintainability nor much other 
>than efficiency - but that's why I use an impure OO for 
>maintainability - but if you rate superiority of a program based on 
>its excellence in functioning, my approach works very well. I 
>contrast with OO designed projects and quite simply, on average they 
>do not perform as well.

Re your last sentence, most OO software sucks because most OO designers
suck.

:-)

One other thing: I re-read what you wrote before and would like to
separate it into two things. You said,

>>>I have never agreed with OO design as my university 
>>>lecturers found out - I quite simply think it's wrong.

I agree with this part wholeheartedly. University lecturers typically
don't know the first thing about how to actually get something done with
OO. They have silly examples, and they tend to teach "purity" as if
some customer actually cares if the code using their particular
guidelines, rules, etc. Most of the time their guidelines are wrong,
and even when the guidelines are right, they are, after all, just
guidelines. The goal is still defined in business terms, not technical
terms. E.g., the schedule, the cost, the functionality, the user
acceptance testing, etc. have nothing to do with purity.

In fact, I disagree with most university lecturers at a deeper level
since I really hate the one-size-fits-all approach. Too many
technologists already know the answer before they hear the question.
They know that Professor Know It All said we should use this technique
and that approach, and even though they don't yet know anything about
the problem, they already know how to solve it! I always find it's
safer to listen before I speak, to ask before I answer. Having a
solution that's looking for a problem is dangerous. Lecturers who
(often implicitly) teach that approach to their students ultimately
cause their students to have just one bullet in their gun. If the
problem at hand *happens* to match the style, approach, and guidelines
that the student believes/uses, everything works great; if not, the
student will do a mediocre job.


>>>Computers
>>>don't work naturally with objects - it's an ill-fit.
>>>
>>>What computers do do is work with data. If you base your design
>>>entirely around data, you produce far superior programs. 

This is the part I was disagreeing about. You can see why, perhaps, in
the example I gave above (the 'Foo' class with 20 derived classes each
of which had its own distinct data structure and algorithm).


>Now regarding the TCO of the code, I would personally say my code is 
>extremely maintainable using my OO-like source filing system. You, I 
>would imagine, would say how can I sleep at night when performing 
>such atrocities to commonly held standards? (you wouldn't be the 
>first to ask this).
>
>Of course, in all this, I am referring to C and assembler and what 
>I'd call C+ because I mostly wrote C with some small C++ extras. This 
>project I'm working on now is the first to use multiple inheritance 
>and templates and a fair bit more.
>
>>Be careful: you are painting yourself into a very narrow corner. You
>>may end up limiting your career as a result.
>
>Possibly, but I would doubt it. I may have some unique opinions on 
>this but what the customer cares about is (a) will it work and (b) 
>can we look after it well into the future. My case history strongly 
>supports both of these criteria, so a priori I'm on the right path.

Well, if you're pretty convinced you're already using the right overall
approach, then you'll have no reason to question that overall approach
and that means it will be a lot harder for you to learn new overall
approaches. To me, the most important area to stay teachable is the big
stuff. The first step in learning something new (especially something
new at the paradigm level, i.e., a new way of approaching software as
opposed to merely a new wrinkle that fits neatly within your current
overall approach) is to a priori decide you might be on the wrong track.
But that's up to you, of course.

(BTW I learn something new at the paradigm level every year or so. I
can't take it more often than that, but I really try to emotionally rip
up *all* my preconceptions and start over every year or so. My buddy
and I get together every year or so, write a paper to tell the world how
great we were and what great things we'd done, then sit back and say to
each other, "That was then, this is now. We're dinosaurs but we don't
know it yet. The weather has probably already changed but we had our
heads down and didn't notice. How has the weather changed? How do we
need to adapt-or-die?" After keeping that up for a few minutes, we
actually begin to believe it, and pretty soon we're looking scared and
worried - like we really could miss the train that's leaving the
station. Ultimately we'd end up reinventing ourselves, coming up with a
new way of approaching projects, stretching, trying out radically new
ideas, and very much not thinking we're a priori on the right path.
It's always be painful and humbling, but it's made me who I am today.
And who I am today is, of course, not good enough any more, so I'll have
to change again! :-)


>>>Now I will 
>>>agree OO is good for organising source for improved maintainability,
>>>but as a design approach I think it lacking.
>>
>>You really should read "OO Design Patterns" by Gamma, et al (also
>>published by Addison Wesley). Read especially chapter 2. I think
>>you'll see a whole world of OO design -- and you'll see ways to use OO
>>at the design level that are totally different (and, I dare say,
>>totally superior) to the approach you are describing here.
>
>Is that about a vector graphics editor called Lexi? I have written 
>two vector graphic editors, the latter in OPL for a Psion Series 3 
>(OPL is quite like BASIC - no objects). Interestingly, the approach 
>Gamma follows is almost identical to my own - I used dynamic code 
>loading to load tool modules with a fixed API thus permitting 
>infinite extensibility. Encapsulation of the API plus building a 
>portable framework are two things I have done many times - I wrote my 
>first framework library in 1992 some four years before going 
>professional.
>
>That Gamma book amused me - it attaches lots of fancy names to real 
>cabbage and thistle programming. However, his conclusion is valid - 
>in modern times, most programmers wouldn't know half that book, and 
>that's worrying - hence the need for such a book.

Yes it does attach some fancy names to blue-collar programming ideas.
The thing I wanted you to see from it was how inheritance is not used as
a reuse mechanism - how you inherit from something to *be* *called* by
(the users of) that thing, not so you can *call* that thing.


>>This project ended up being around half a person-millennium (150-200
>>developers over a 3 year period). I ended up training and mentoring
>>them all, and we had lots and lots of design sessions. When they were
>>finished, the things that used to take 9 months could be done by a
>>single person in less than a day. The success-story was written up in
>>Communications of the ACM -- it was the lead article in the Special
>>Issue on Object-Oriented Experiences. It was also written up in IEEE
>>Software and perhaps a few other places. (And, by the way, there was
>>no loss of performance as a result. That was *very* hard to achieve,
>>but we did it. In the end, customers gained 2x MIPS/dollar.)
>>
>>The point is that these benefits came as result of OO *design*, not as
>>a result of programming-level issues.
>
>I'm sure OO design greatly improved the likely wasp's nest of 
>spaghetti that existed in there previously. But I'm not seeing how OO 
>design is better than any other approach from this example - there 
>are many methods that could have been employed to achieve the same 
>result.

Two things:

1. If what you said in the last sentence is true, where's the beef? If
these other approaches could do the same thing, why didn't they?

2. I think you've missed the point I was making. The point was that
this project used inheritance the way I'm proposing it should be used,
and that's very different from the "inheritance is for reuse" approach.
It's not about OO vs. non-OO. It's about how the two different styles
of OO produce different results.


>>One more example: UPS (another of my clients; in fact I was there just
>>last week) has new "rating" and "validation" rules that change every 6
>>months. For example, if Detroit passes a law saying it's no longer
>>legal to drive hazardous materials through its downtown area, the code
>>needs to change to prevent any package containing hazmat from going
>>through downtown Detroit. In their old system, which was built using
>>your style of C++, it took 5 months out of every 6 to integrate these
>>sorts of changes. Then someone created a framework using OO design
>>(not just C++ programming), and as a result, they could do the same
>>thing in 2 weeks.
>
>Any good framework here, OO or not, would have solved most of their 
>dynamic change problem. In fact, I'd plug in some sort of scripting 
>capability so such items were easy to change.

Again I think you're missing the point. The point was similar to the
above: inheritance-to-be-reused vs. inheritance-for-reuse.


>>>An example: take your typical novice with OO. Tell them the rules and
>>> look at what they design. Invariably, pure OO as designed against
>>>the rules is as efficient as a one legged dog. 
>>
>>The way you have learned OO, yes, it will have performance problems.
>>But the way I am proposing OO should be done, either it won't have
>>performance problems at all, or if it does, those problems will be
>>reparable.
>
>ie; You're bending OO to suit real-world needs, 

Not necessarily. I'm certainly not a believer in purity of any sort,
and I'm certainly willing to eject *anybody's* definition of purity to
achieve some greater goal. But I've also used OO *to* *do* performance
tuning. That is, I've used various OO idioms and design approaches as a
way to dynamically select the best algorithm for a particular task. The
OO part of that is the selection, and is also the pluggability of new
algorithms as they become known, and is also the fact that we can mix
and match pieces of the problem using one algorithm and other pieces
using another (as opposed to having one function 'f()' that has a single
algorithm inside). I'm not saying none of this could be done without
OO; I am rather saying that the performance tuning was, in some cases,
within the spirit of what OO is all about.


>which is precisely 
>what I said experienced OO people do.

Certainly I'm both willing and have done so. But I don't seem to view
that like you seem to view it. I don't see it as if something is
lacking in OO. Rather I see it like I have a toolbox, and one of those
tools says "OO," and I end up choosing the right combination of tools
for the job. When I combine OO with non-OO, that doesn't bother me or
indicate something is wrong with either OO or non-OO.

In contrast, it feels like what you're saying is, "If you can't do the
*whole* thing using OO, something is wrong with OO." 


>>>In fact, in my opinion, OO 
>>>experience is actually learning when to break pure OO and experienced
>>> OO advocates do not realise that they so automatically break the
>>>pure application of what they advocate.
>>
>>We agree that purity is never the goal. Pure OO or pure procedural or
>>pure anything else. The goal is (or *should* be) to achieve the
>>business objectives. In my experience, OO *design* brings the real
>>value, and not just programming-level issues.
>
>Has it not occurred to you that it's merely a /consequence/ of OO 
>rather than innate quality that it has these beneficial effects?

I don't think either one is true.

1. Re "a /consequence/ of OO": I think the benefits are a consequence of
clear thinking, not OO. OO helps organize the design, and helps by
providing snippets of design that have been used elsewhere. And OO
programming helps if and only if the design uses OO. But any given OO
hierarchy could be designed in two totally different ways, one sideways
from the other, basically swapping methods for derived classes, so
therefore the benefits are not a consequence to OO itself - they are a
consequence of clear thinking and proper use of software technology in
general.

2. Re an "innate quality that it has": OO certainly does NOT innately
have any beneficial effects. You can write FORTRAN code in any
language, including C++ or Java or Smalltalk or Eiffel, and certainly a
misuse of OO (or of an OO programming language) can produce problems
that are worse than any benefits they might accrue.


>>I agree with everything except your last phrase. OO design is good
>>for both people and computers.
>
>Right, firstly, before I start this section, I'd like to thank you 
>for your time and patience - I've noticed some of what I didn't know 
>and you explained to me was already online in your FAQ, so I 
>apologise for wasting your time in this regard. Furthermore, I should 
>mention that if you give me permission to distribute this 
>correspondence, you will not only have done me a great favour but 
>also the same to others. 

Go for it.

>Certainly, if it takes you as long to reply 
>as me, you're investing considerable time which a busy man as 
>yourself surely cannot easily spare.
>
>I, as I have already mentioned, come from a rather unique programming 
>background. We were probably most comparable to the Unix culture 
>except we were more advanced and we always had a very strong free 
>software tradition where we released code and source into the public 
>domain - furthermore, many commercial apps came with source too. 
>Hence, there was great chance for learning off others, and much of 
>this recent furore about OO etc. in my humble opinion is merely fancy 
>names for a collection of old techniques.
>
>Now as I mentioned a number of times, I believe a data-centric 
>approach is superior to OO because it more accurately fits the way a 
>computer works. This is not to say many of the advantages of OO do 
>not still hold - in fact, I daresay many OO experts actually are data-
>centric too without realising it. My criticism of OO therefore is 
>that it isn't /intuitively/ "correct" ie; pure OO is rarely the 
>optimal solution.

Here again, you seem to be saying that if OO isn't optimal for 100% of
the solution, then something's wrong with it. I take the opposite tact,
mainly because I am *not* a promoter for any given language or paradigm.
In fact, I would be highly suspicious if someone (including you) claimed
to have a technique that is optimal for 100% of the solution to any
given problem, and especially if it was optimal for 100% of the solution
of 100% of the problems. I simply do not believe that there exists any
one-size-fits-all techniques, including OO, yours, or anybody else's.


>I had an idea back in 1994 for advancing procedural programming to 
>the next level (this was independent of OO - honestly, I barely even 
>knew what it was at the time) - I effectively wanted to do what OO 
>has done in knocking us onwards a notch - however, as it would be, I 
>considered then and still do today that my solution is superior.
>
>Basically, it revolves entirely around data. Responsibility for data, 
>whether in memory, disc or across a network is devolved entirely to 
>the kernel. One may create data streams between data in an arbitrary 
>fashion - how it actually is peformed (translations etc.) is however 
>the kernel sees fit. Data is strongly typed so you can't stick 
>incompatible types of data together - however data can be converted 
>from one type to another via convertors which are essentially 
>specialised plug ins which can be installed. Often, conversion is 
>implicitly performed for you although either you can choose a route 
>or it can dynamically create one based on best past performances. Of 
>course, converters can offer out their input in more than one format 
>or indeed offer a compound document as some or all of its subdatas.
>
>Now the next part of the picture is components - these are tiny 
>programs which do one thing and one thing well to data. A good 
>analogy would be "more" or "grep" in Unix - but it goes way beyond 
>that because components are much like a COM object or Qt Widget in 
>that you can just plonk them somewhere and they do their thing. Then, 
>the theory is, to build any application, you merely create a *web* of 
>simple data processing components. For example, a spell checker 
>component would accept text data and check it either with the user or 
>with the component managing the data - there is no concept of data 
>ownership in my proposal (kernel owns everything)
>
>This model, I believe, compares extremely well to OO. You get lots of 
>code reuse, a dynamic and extremely flexible linking mechanism, a web 
>rather than a hierarchy and automatic distribution across multiple 
>processors (and indeed machines). It's clearly functionally biased 
>because it simply sets up the data relations and the kernel works out 
>the best way to actually perform the processing. You get lots of 
>stuff for free eg; OLE, data recovery in case of program crash and 
>indeed limited graphical programming like some of those UML editors. 
>You get the advantages of dynamic linking without business' dislike 
>of source exposure as with Java or VB.
>
>Furthermore, you get automatic /data/ reuse as well as code reuse - 
>data just as much as code can be distributed across multiple machines 
>for performance and/or security reasons. And of course, maintainence 
>costs are low because the component set you use are as individual or 
>fine-grained as you like them.
>
>Now hopefully you'll be agreeing with me that this is all good - 
>however, if you're like the other experts I've proposed this to, your 
>first question will be "oh but how to implement it?" because the 
>balancing act between all the different requirements means severe 
>inefficiency. And you'd be right - I've made two prior attempts at 
>this and failed both times - and right now, I'm making my third 
>attempt which I'm self-financing myself for six months. The theory 
>goes, produce a technology demonstration, if it runs at all 
>reasonably then obtain venture capital, start a company and two years 
>later we have a product. Five years later it's more or less complete. 
>If anything goes wrong, return to working on whatever pays a lot for 
>a while, then try again in a few years. Either way, the spin off 
>benefits of each past attempt have been enormous, so really I can't 
>lose.
>
>So, thoughts? I'm particularly interested in what you see as design 
>flaws 

Please compare and contrast with web-services. Obviously you're not
married to XML like most web-services are, but they also have a concept
of components / services through which data flows. Is there some
similarity? Even at the conceptual level?


>- I 
>know MIT did research into this for a while but stopped. Would you 
>agree it's 
>a viable future? 

Actually my first question about viability is not about how to implement
it efficiently, but instead *assuming* you find a way to implement it
efficiently, will anybody buy into it anyway? Large companies pay
roughly half a year's salary per programmer to train their people in a
new style of programming, such as going from non-OO to OO. They also
pay roughly 1/3 that for tools and other support stuff. Plus the
managers have to learn enough about what's going on to manage it
effectively, and that's scary for them, particularly the older guys who
are close to retirement ("Why should I bother learning something new? I
might not be any good at it! I'm in control now, so even if what we're
doing isn't maximally efficient to the company, changing it would risk
the delicate balance that keeps me employed, so what's in it for me to
change?").

If we figure $200K (US) per programmer (burdened salary including
hardware, software, benefits, management overhead, building, air
conditioning, etc.), and if we figure a medium sized organization with
100 programmers, then we're talking about $130M to embrace this new
paradigm. Will they really derive a benefit that is equivalent to at
least $100M? What's the pay-back? And if these medium sized companies
don't ante up for your new approach, why should the little guys do it?

I'm not trying to discourage you - just trying to ask if you know what
the pay-back really is. I'm also trying to remind you about how none of
the front runners in OO survived, and ultimately it took a couple of
decades before that paradigm took hold.


>I've had Carl Sassenrath (he did much the OS for the 
>Commodore Amiga) and Stuart Swales (did much of RISC-OS I mentioned 
>earlier) both agree it's probably right, but both wondered about 
>implementation. I should be especially interested in seeing what a 
>static OO 
>based person things - neither Carl nor Stuart are static code nor OO 
>advocates hugely.

Please explain what you mean by "static code."


>Furthermore, any advice about soliciting venture capital in Europe 
>would be 
>useful (yes, I know it's like squeezing blood from a stone here) - 
>ever since 
>the indigenous industry withered and died here, it's been very hard 
>to obtain 
>capital for blue-sky projects without the Americans buying them up. 

It's also hard here. There really has to be a decent business case.
Honestly, if you're wanting to make money, you're better off using the
Harvard Business School model: sell it first, *then* figure out how to
deliver it. Building a better mousetrap and hoping somebody out there
cares is an extremely risky business. Sometimes it works, but I think
most of those are historical accidents - they happened to accidentally
build the right thing at the right time.

Seems to me you have two goals: stretch yourself via your idea (and in
the process learn something new), and build a business that helps you
keep the lights on, and possibly more. If the latter is your primary
goal, seriously thinking about starting with a business case or even
some sales attempts. If the former, then ignore everything else I've
said and have a wonderful six months -- and may be, just may be, you'll
hit the jackpot and actually sell the thing when you're done.


>I'm 
>unable to obtain a work visa to the US (on the banned list), so 
>that's out - and 
>besides, as far as I can see, only IBM out of the big US software 
>companies 
>would be interested as only IBM's goals would be advanced by such a 
>project. Oh BTW, did I mention it runs on Win32/64, Linux and MacOS X 
>when they get the new FreeBSD kernel in - and yes, all computer 
>irrespective 
>of endian automatically work in unison. I'd also like it to stay in 
>Europe so it (or rather I) stays free from software patents.
>
>Anyway, any comments you may like to offer would be greatly 
>appreciated. You've already earned yourself an acknowledgement in the 
>projects docs for helpful tips and suggestions.

Thanks for the acknowledgements.

Okay, here's another comment for you to think about (no need to reply):
What specific application area will it be most suited for? Will it be
most suited for embedded systems? handhelds? web servers? apps
servers? client-side programs? thin-client? Similarly, what specific
industries are you seeing it fit best? Banking, insurance,
transportation, etc.

May be these questions don't make sense, because may be I'm giving the
wrong categories. E.g., may be the real question is whether it will be
developing software development tools, database engines, or other
"horizontal" apps, as opposed to the various vertical markets. Whatever
the correct categorization is, think about whether you know who would
use it and for what (and please forgive me if I missed it above; it's
very late). Ultimately if you have a reasonably crisp sense of who
would buy it, you will be better able to target those programmers.
Programmers nowadays want highly customized tools. You mentioned GUI
builders earlier. I personally despise those things, since they have
dumbed down programming and turned it into something more akin to
brick-laying, but I recognize that programmers nowadays really crave
those easy-to-use tools. Many programmers nowadays don't seem to like
writing code. They don't mind dragging the mouse on the screen, and
clicking on a pull-down list of options, but many of them really don't
want to actually type in code. I'm the opposite. Give me an Emacs and
a command-line compiler, then stay out of my way. Which really means I
no longer "fit in" anywhere. Oh well, that's life I guess.

I wandered - let me get back on track. The point was that programmers
nowadays don't want generic tools that can be customized for their
industry. They want specific tools that have already been customized
for their industry. They want tools that are brain-dead easy to use,
tools that don't require them to write much code. Will your vision be
able to serve those sorts of programmers? If so, is the plan to do the
customization with the VC money?

My not-so-hidden-agenda here is to help you begin to think like a
"packaged software" company. Unlike custom software companies and
end-user shops that build apps based on a spec from a well-defined
"customer," the packaged-software companies spend *enormous* amounts of
time and money doing market research before they write anything. Here
again it's the Harvard B-school approach: they're figuring out what will
sell, then they build that. So think about that: what will sell? is
there any latent frustration out there that would spur companies into
changing their toolset and programmers? what is that frustration?

(I wish I could help you by answering some of these questions, but
frankly the frustration I'm seeing these days isn't, "We're having
trouble delivering value because _____", but is instead, "We're having
trouble because we're being asked to do the same tasks as always but
with 25% fewer people," or "We're having trouble because we're not
viewed as adding any business value to the company." In other words,
I'm just not seeing a great groundswell of frustration from technical
people and/or first-line managers who are moaning in unison, "Our tools
and/or languages suck; if we only had better tools and/or languages,
life would be wonderful." I don't see why a new tool and a new language
(and *especially* a new language within a new paradigm) would be viewed
by the typical manager as a cost-saving measure. Like I said earlier,
I'm not trying to throw a wet towel on your idea; more just being honest
about what I do and don't see in the marketplace today.)

Marshall




From: Niall Douglas <[email protected]>
To: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Thu, 1 Aug 2002 01:12:45 +0200

On 30 Jul 2002 at 2:14, Marshall Cline wrote:

> >Not being able to obtain these books easily (I live in Spain plus
> >money is somewhat tight right now), I looked around the web for more
> >on this. I specifically found what not to do when inheriting plus how
> > deep subclassing usually results in code coupling increasing. Is
> >that the general gist?
> 
> That's a start. But coupling between derived and base class is a
> relatively smaller problem than what I'm talking about. Typically
> deep hierarchies end up requiring a lot of dynamic type-checking,
> which boils down to an expensive style of coding, e.g., "if the class
> of the object is derived from X, down-cast to X& and call method f();
> else if it's derived from Y, down-cast to Y& and call g(); else if
> ...<etc>..." This happens when new public methods get added in a
> derived class, which is rather common in deep hierarchies. The
> if/else if/else if/else style of programming kills the flexibility and
> extensibility we want to achieve, since when someone creates a new
> derived class, they logically need to go through all those
> if/else-if's and add another else-if. If they forget one, the program
> goes into the "else" case, which is usually some sort of an error
> message. I call that else-if-heimer's disease (pronounced like
> "Alzheimer's" with emphasis on the word "forget").

When you say dynamic type checking, you mean using typeid()? I had 
thought that was to be avoided - in fact, any case where a base class 
needs to know about its subclasses indicates you've got the 
inheritance the wrong way round?

> [stuff about bad class design deleted]
> However since we used inheritance, we're pretty much stuck with
> HashTable forever. The reason is that inheritance is a very "public"
> thing -- it tells the world what the derived class *is*. In
> particular, users throughout our million-line-of-code system are
> passing Bags as HashTables, e.g., converting a Bag* to HashTable* or
> Bag& to HashTable&. All these conversions will break if we change the
> inheritance structure of Bag, meaning the ripple effect is much
> higher.

One of the things I look for when designing my class inheritances is 
whether I could say, chop out one of the base classes and plug in a 
similar but different one. I'm just trying to think where I learned 
that lesson - I think it was that cellular automata game I wrote to 
refresh my C++ (http://www.nedprod.com/programs/Win32/Flow/) and I 
realised it's best when the base class knows nothing at all about its 
subclasses except that it has some and furthermore than subclasses 
know as little as possible about what they inherit off (ie; they know 
the inherited API obviously, but as little as possible about what 
/provides/ the API). I think, if memory serves, it had to do with the 
tools in the game. Of course, officially this is called reducing 
coupling.

> A derived class's methods are allowed to weaken requirements
> (preconditions) and/or strengthen promises (postconditions), but never
> the other way around. In other words, you are free to override a
> method from a base class provided your override requires no more and
> promises no less than is required/promised by the method in the base
> class. If an override logically strengthens a
> requirement/precondition, or if it logically weakens a promise, it is
> "improper inheritance" and it will cause problems. In particular, it
> will break user code, meaning it will break some portion of our
> million-line app. Yuck.
> 
> The problem with Set inheriting from Bag is Set weakens the
> postcondition/promise of insert(Item). Bag::insert() promises that
> size() *will* increase (i.e., the Item *will* get inserted), but
> Set::insert() promises something weaker: size() *might* increase,
> depending on whether contains(Item) returns true or false. Remember:
> it's perfectly normal and acceptable to weaken a
> precondition/requirement, but it is dastardly evil to strengthen a
> postcondition/promise.

This is quite ephemeral and subtle stuff. Correct application appears 
to require considering a lot of variables.

> Please don't assume the solution is to make insert(Item) non-virtual.
> That would be jumping from the frying pan into the fire, since then
> Bag::insert() would get called on a Set object, and there actually
> could be 2 or 3 or more copies of the same Item inside a Set object!! 
> No, the real problem here isn't the override and it isn't the
> virtualness of the method. The real problem here is that the
> *semantics* of Set are not "substitutable for" those of Bag.

This is quite a similar point to what you made two replies ago or so. 
I'm not sure of the distinction between this example and the one 
which you explained to me a few emails ago - ie; this example is 
supposed to prove deep inheritance trees are evil but yet it would 
seem you are proving the same point as before regarding bad 
inheritance.

Or are you saying that the deeper the tree, the much greater chance 
it's a symptom of bad design?

> As before, aggregation would be perfectly safe and reasonable here:
> Dictionary could have-a Set, could insert Association objects (which
> would automatically be up-casted to Item&), and when it
> accessed/removed those Items, Dictionary could down-cast them back to
> Association&. The latter down-cast is ugly, but at least it is
> logically safe -- Dictionary *knows* those Items actually are
> Associations, since no other object anywhere can insert anything into
> the Set.
> 
> The message here is NOT that overrides are bad. The message here is
> that tall hierarchies, particularly those built on the "inheritance is
> for reuse" mantra, tend to result in improper inheritance, and
> improper inheritance increases time, money, and risk, as well as
> (sometimes) degrading performance.

So, let me sum up: inheritance trees should be more horizontal than 
vertical because in statically typed languages, that tends to be the 
better design? Horizontal=composition, vertical=subclassing.

No, I've got what you mean and I understand why. However, the point 
is not different to what I understood a few days ago although I must 
admit, Better = Horizontal > Vertical is a much easier rule of thumb 
than all these past few days of discussion. You can use that rule in 
your next book if you want :)

> * If Base::f() says it never throws an exception, the derived class
> must never throw any exception of any type.

That's an interesting one. I understand why already, I can infer it 
from above. However, if the documentation of my subclass says it can 
throw an exception and we're working with a framework which 
exclusively uses my subclasses, then all framework code will 
ultimately have my code calling it ie; bottom of the call stack will 
always be my code. Hence, in this situation, it is surely alright to 
throw that exception?

I say this because my data streams project can throw a TException in 
any code at any point in time (and there are explicit and loud 
warnings in the documentation to this regard). Of course, one runs 
into problems if the base class when calling its virtual methods does 
not account for the possibility of an exception.

> >Hence, that TSortedList should now derive off QGList which doesn't
> >have the append and prepend methods so I can safely ensure it does
> >what its parent does.
> 
> What you really ought to do is check the *semantics* of QGList's
> methods, in particular, read the preconditions and postconditions for
> those methods. (I put these in the .h file for easy access, then use
> a tool to copy them into HTML files; Qt seems to put them in separate
> documentation files; either way is fine as long as they exist
> somewhere.) Inheritance is an option if and only if *every* method of
> TSortedList can abide by the corresponding preconditions and
> postconditions in QGList.

Actually, to tell you the truth, I had already looked through QList 
and QGList to ensure my altering of TSortedList wouldn't cause 
problems - those disabled methods weren't called internally within 
QGList, so I was fairly safe the list would always remain sorted.

However, I hadn't been impressed with the gravity of decisions like 
that. That's different now.

> Again, in a small enough project, you can use "improper inheritance"
> if you want, but you must be very sure that no one ever uses a Base&
> or Base* to point to a Derived object. (Personally I never use
> improper inheritance, since the down-side cost is unlimited. In
> contrast, most "bad programming practices" have a bounded cost, e.g.,
> a "goto" might increase the maintenance cost of its method, but it can
> never screw up any other method, so its cost is bounded by the number
> of lines in the method. However the down-side cost for improper
> inheritance goes on and on: the more users use your code, the more
> places that can break.)

It seems to me that one should avoid these cases as well - but if you 
do have to do it, then a great big flashing neon warning sign in the 
docs is in order.

> >If more warning were out there, we'd all have less 
> >problems with other people's code.
> 
> Agreed. I get on my stump and shake my fist in the air every chance I
> get. You are hereby deputized to do the same. Go get 'em!!
> 
> Seriously, proper use of inheritance really is important, and knowing
> the difference is critical to success in OO, especially in large
> systems.

Already have a page planned called "programming guidelines". It's 
mostly about applying the data-centric approach correctly and 
efficienctly, but it'll have a section about writing good C++ as 
well. I'll put a recommendation in for your book - I notice my old 
buddies at ACCU (it's UK ACCU, quite distinct from US ACCU) gave your 
book the thumbs-up which is very rare (http://www.accu.org/cgi-
bin/accu/rvout.cgi?from=0ti_cp&file=cp000371a). They normally slam 
almost everything they review - and in fact, your book didn't make 
the recommended reading list 
(http://www.accu.org/bookreviews/public/reviews/0hr/advanced_c__.htm).

> >So why my class, almost identical, does not and Qt's one does I do
> >not know.
> 
> Trust me: change "void TSortedList<class type>::f() { ... }" to
> "template<class type> TSortedList::f() { ... }".

I feel slightly sheepish now. It's quite obvious really :( - still, 
last time I can't tell you the joy I felt when it finally linked for 
the first time!

> >> while (*dest++ = *src++)
> > [...]
> >That kind of dangerous code brought out a compiler bug in a version
> >of GCC and MSVC 5 if I remember correctly. The increments weren't
> >always done with the load and store when full optimisation was on.
> >Solution: use comma operator.
> 
> I'll take your word for it, and I really don't want to argue over it,
> but I'm *very* surprised that any C compiler ever shipped any version
> that couldn't correctly compile the above. That snippet is an idiom
> that is used in many, many C programs, and is probably part of the
> compiler's test suite.

Only on full optimisation. What happens is the loop exits without 
incrementing either dest or src correctly - often, this is 
unimportant as rarely you'd use dest or src afterwards in cases like 
above. Very architecture specific - a RISC architecture is more 
likely to have this problem than a CISC.

> I suppose some optimizers might unroll some loops, but the real
> problem, as you obviously know, is cache misses. Plus compilers can't
> necessarily know that any given loop will actually be a bottleneck,
> and as you know, performing this sort of optimization on a
> non-bottleneck loop would make space-cost worse without any
> improvement in overall time-cost. If a program has 1,000 loops, how
> could a compiler guess which of those are the bottlenecks? As has
> been demonstrated by software engineering study after study over the
> years, programmers don't even know where their own bottlenecks are, so
> I expect it will be a long time before compilers can know.

It knows using a "make the loop code x% of the processor L1 cache" 
approach. If there's lots of code in loop, it gets unrolled much 
less. Obviously, also, the writer gives hints with #pragma's.

More recently, we've seen the introduction of intelligent branch 
predictors (old days they did a dest - PC compare, if negative it 
assumed the branch would be taken) which effectively cache branch 
decisions and assign a history profile. That saves those tricks I 
mentioned before about removing if...else's and routing the decision 
through effectively a state-table.

Of course, Intel went ever further with the Pentium 4 and had the 
thing execute the code either side of the branch and throw whichever 
not taken away. We really really need to get away from x86 - the ARM 
is some obscene number of times more efficient per clock cycle than 
any x86 processor and it was designed around 1986 (on the back of an 
envelope down the Wrestlers pub in Cambridge if you're curious)! 
Amazingly for a guy drinking a pint of beer, he did a better job than 
DEC did with the Alpha instruction set despite them spending millions 
:)

> >If you ask me about embedded systems, I don't doubt I'm as good as
> >they get.
> 
> Sounds like it. I should remember that in case I get more embedded
> systems work from TI or UPS or HP. In the mean time, learn proper
> inheritance so you'll be ready for my phone call! :-)
> 
> (Don't sit by the phone waiting for it to ring - I don't have anything
> 'hot' right now.)

Well, given I can't obtain a US work visa, I doubt I'd be a suitable 
candidate anyway! However, I will tell you something - in my early 
days I worked with some very high level engineers who I learned 
bucket-loads off. Since then, *I've* been the high level engineer 
doling out the buckets and to be honest, I could do with getting back 
under the wing of someone really good. Otherwise, as you mentioned 
below, it's easy to stale because you're not getting challenged in 
your presumptions enough.

Problem, as always, is finding a good wing. They're a rare prize 
nowadays.

> >All this high-level stuff though I must admit is beyond me 
> >a bit. 
> 
> As you know, embedded systems programming is highly technical, and
> presents enough of a challenge that the weak tend to get killed off -
> they end up programming way up at the apps level using something soft
> and gushy like Visual Basic or JavaScript. So the only survivors in
> embedded systems are technically tough enough, at least at the
> programming level.
> 
> Unfortunately most embedded systems programming doesn't also force
> people to be really good at the design level. Most embedded systems
> work is intense at the binary-level, always trying to squeeze 10
> kilograms of stuff in a bag meant to hold only 5 kilograms. I think
> that's especially true in the hand-held environment, but either world
> tends to produce hot-shot programmers who can program their way out of
> most situations, and aren't necessarily great at the high-level stuff.

Actually, those embedded systems are becoming disgustingly powerful - 
that GPS receiver was a 80Mhz 32 bit processor with 16Mb of RAM and 
optionally 64Mb of ROM. On that, you can write your applications in 
Visual Basic and it'll actually go. Of course, that's what Windows CE 
is all about.

Embedded systems programming as we knew it is dying out. When 
desktops went all powerful, a lot of us assembler guys went into tiny 
systems but now they've gone all powerful, it's a rapidly shrinking 
market. The writing is definitely on the wall - move onto OO and C++ 
and such or else become unemployable.

> But you'll make it - I can tell. You've already embraced the key
> elements of good OO design (except for understanding that OO design
> really means the structure of your inheritance relationships, and that
> your algorithms are pluggable/replaceable and end up getting buried in
> derived classes; more on that later).

Aw, thanks for the show of confidence! I think anyone who is flexible 
enough to always be learning how to improve inevitably will succeed. 
As the chinese say, it's all about the reed bending in the wind.

> >Have you noticed the world's most popular programming languages tend
> >to be evolved rather than designed? ;)
> 
> Yes, and I think there's a reason. You might not like my reasoning,
> but it goes like this: Businesses choose programming languages based
> on business issues first and technology issues second. This is not a
> bad thing. In fact, I believe businesses *ought* to worry primarily
> about business issues such as acquiring skilled people and tools. Can
> we hire programmers that already know this language? Are there a glut
> of programmers, or are we going to have to pay enormous salaries,
> signing bonuses, and relocation fees? Are the area universities
> churning out bodies that know this language? Are the programmers any
> good? Is there a ready supply of programmers we can "rent" (AKA
> contractors) so we don't have to hire everyone? Are there outsourcing
> firms we can bring in to finish the work as a contingency plan? Is
> there a ready supply of consultants who can advise us on nuances of
> using this language? Those are examples of the people-questions; next
> come a similar pile of questions about tools, multiple vendors,
> long-term support, development environments, maturity of tools,
> companies who can train our people in using the tools, etc., etc.

Have you heard that someone somewhere has decided to lower the cost 
of IT to industry by farming it out to the third world like 
semiconductors etc? Possibly it's mostly UK, but see 
http://www.contractoruk.co.uk/news040702.html. There'll be an article 
from me in the same periodical soon where I urge specialisation to 
prevent the inevitable massive job losses and heavy reduction in 
earnings.

> And after all these questions are answered, somewhere down on the list
> are things like the relative "cleanliness" of the language. Are the
> constructs orthogonal? Is there appropriate symmetry? Are there
> kludges in the syntax? Those things will effect the cost of the
> software some, to be sure, but they aren't life-and-death issues like
> whether we can buy/rent programmers or whether we can buy/license good
> tools. I have a client that is using (foolishly) a really clean,
> elegant language that almost nobody uses. Most programmers who use
> that language for more than a month absolutely love it. But the
> client can't buy or rent programmers or tools to save its life, and
> its multi-million dollar project is in jeopardy as a result.

What's the language?

> So far all I've said is that most businesses choose programming
> languages based primarily on business considerations, not primarily on
> technical considerations. There are some exceptions (such as the
> company I just mentioned), and perhaps you even experienced one or two
> exceptions, but I think almost anyone would agree that the basic
> premise ("most businesses choose...") is correct. I further assert
> that that is a good thing, and you are free to disagree on that point,
> of course. However I have to believe you agree with me regarding how
> things *are*, even if you disagree with me about how things *ought* to
> be.
> 
> The conclusion of the argument is simple: Go back through the
> business-level questions I mentioned above, and most if not all of the
> answers would be "okay" if the language was an incremental extension
> of some well-known, mature language. That means using an "evolved"
> language lowers business risk, even if it adds technical warts or
> reduces technical elegance. (At least it's *perceived* to lower
> business risk, but business people make decisions based on perception
> rather than reality anyway, so the perception of a reduced business
> risk is a powerful argument in favor of an "evolved" language.)

Actually, I completely agree with everything you've said. Already had 
twigged it to be so!

And a personal component is that I've tried my hand with designed 
languages eg; Haskell or Java. And I found them annoying (although 
Haskell is seriously seriously powerful) for various reasons. My own 
personal thought is that designed languages may technically be 
perfect, but much of writing software is an art more than engineering 
and hence designed languages are often too sterile for my tastes.

You probably won't like me saying this, but half the reason why I 
like C and C++ is because they permit me to be really really stupid 
if I want to. It's a very personal reason, but I think a lot of 
programmers feel the same.

> Yes, C is closer to the machine, since its mantra is "no hidden
> mechanism." C++ *strongly* rejects the no-hidden-mechanism mantra,
> since its goal is ultimately to hide mechanism - to let the programmer
> program in the language of the *problem* rather than in the language
> of the *machine*. The C++ mantra is "pay for it if and only if you
> use it." This means that C++ code can be just as efficient as C code,
> though that is sometimes a challenge, but it also means that C++ code
> can be written and understood at a higher level than C code -- C++
> code can be more expressive -- you can get more done with less effort.
> Of course it is very hard to achieve *both* those benefits (more done
> with less effort, just as efficient as C) in the same piece of code,
> but they are generally achievable given a shift in emphasis from
> programming to design (using my lingo for "design"). In other words,
> OO software should usually put proportionally more effort into design
> than non-OO software, and should have a corresponding reduction in the
> coding effort. If you're careful, you can have dramatic improvements
> in long-term costs, yet keep the short-term costs the same or better
> as non-OO.

That's an interesting point - that as the languages evolve, more time 
proportionally needs to go into design.

> People who don't understand good OO design (my definition, again;
> sorry) tend to screw things up worse with OO than with non-OO, since
> at least with non-OO they don't *try* to achieve so many things at
> once -- they just try to get the thing running correctly and
> efficiently with hopefully a low maintenance cost. In OO, they try to
> use OO design (my defn) in an effort to achieve all those *plus* new
> constraints, such as a dramatic increase in software stability, a
> dramatic reduction in long-term costs, etc. But unfortunately, after
> they spend more time/money on design, they have a mediocre design at
> best, and that mediocre design means they *also* have to pay at least
> as much time/money on the coding stage. They end up with the worst of
> both worlds. Yuck.
> 
> The difference, of course, is how good they are at OO design (using my
> defn).

I would personally say it's about how good they are at *design* full 
stop period. I still hold that it's unimportant whether you use OO or 
not - it's merely one of the tools in the toolbox and its merit of 
use entirely depends on the situation.

> It shouldn't. Try this code and see if it causes any errors:

Actually, I tried:
--- cut ---
class BaseString {
public:
BaseString(const char* s);
BaseString &operator=(const char *);
};

class DerivedString : public BaseString {
public:
DerivedString();
DerivedString(const BaseString &s);
DerivedString(const char* s);
DerivedString &operator=(const char *);
};

int main()
{
DerivedString foo("foofoo") ;
foo = "Hello world";
return 0;
}
--- cut ---

> I think that properly represents the problem as you stated it:
> >>>TQString foo;
> >>>foo="Hello world";
> >>>
> >>>Now TQString is a subclass of QString, and both have const char *
> >>>ctors. The compiler will refuse to compile the above code because
> >>>there are two methods of resolving it. "
> 
> Let me know if the above compiles correctly. (It won't link, of
> course, without an appropriate definition for the various ctors, but
> it ought to compile as-is.)
> 
> If the above *does* compile as-is, let's try to figure out why you
> were frustrated with the behavior of TQString.

Yes, it compiles fine. And no, I'm not sure why it does when TQString 
does especially when I've faithfully replicated the constructor 
hierarchy above.

> >I'm running into similar problems with the << and >> operators - I've
> > subclassed QDataStream with TQDataStream because QDataStream is
> >default big endian and doesn't provide support for 64 bit integers. 
> 
> Again, I'd suggest trying something different than subclassing
> QDataStream, but I don't know enough to know exactly what that should
> be.
> 
> >Every single time I use << or >> I'm an ambiguous resoluton error
> >when clearly the source or destination object is a TQDataStream.

I got it to work by copying every operator<< and >> into my 
TQDataStream, and have it do:
TQDataStream &operator>>(u8 &i) { return 
static_cast<QDataStream>(*this) >> (s8 &) i, *this; }

... for each one. I figure it's not inheriting the << and >> 
operators from QDataStream correctly? Maybe something similar is 
happening to TQString. I'll investigate.

> >Most of my C++ problems are arising from "repairing" Trolltech's
> >code. 
> 
> And, I would suggest, mostly because you are repairing it via
> inheritance.

That's because (a) Trolltech will make their classes like mine in the 
future (b) I don't want my users having to learn new classes and (c) 
there are a fair few times you need to pass my derived classes into 
Qt. I've done this by casting up with either static or dynamic casts. 
This is okay given I've mostly followed the rules you gave before 
about extending functionality and not changing existing 
functionality.

> >> [overloading based on return type]
> >>Another C++ idiom lets you do just that. I'll have to show that one
> >>to you when I have more time. Ask if you're interested.
> >
> >Is that like this:
> >bool node(TQString &dest, u32 idx)
> >bool node(TKNamespaceNodeRef &ref, u32 idx)
> >...
> 
> Nope, I'm talking about actually calling different functions for the
> following cases:
> 
> int i = foo(...);
> char c = foo(...);
> float f = foo(...);
> double d = foo(...);
> String s = foo(...);

Ok, I'm interested now. You can point me at a webpage if one exists.

> >1. Why didn't C++ have separated support for code reuse and subtyping
> > (like Smalltalk)?
> [explanation chopped]
> So if C++ wanted to be like Smalltalk, it could do what you want. But
> given that C++ wants compile-time type-safety, it can't do what you
> want.

I personally would probably have had it use static typing when it 
could, but when the compiler didn't know it would complain unless you 
added a modifier to say it was a dynamic cast - then the check gets 
delayed till run time. As it happens, surely that's happened anyway 
(albeit relatively recently) with dynamic_cast<>().

My point is, it could have been made possible to utilise the best of 
both worlds but with a bias toward static typing.

> >2. Why don't return types determine overload?
> 
> Because things like this would be ambiguous:
> 
> int f();
> float f();
> char f();
> 
> int main()
> {
> f();
> ...
> }

That's easy - if there's an f() returning void, it's the correct one 
to call. If there isn't, it's a compile error - you'd need (char) f() 
or something to say which to call.

> Worse, if the three 'f()' functions were compiled in different
> compilation units on different days of the week, the compiler might
> not even know about the overloads and it not notice that the call is
> ambiguous.

That can happen anyway surely if you're talking different scopes?

> There's an interesting example in Bjarne's "Design and Evolution of
> C++" that shows how type safety would commonly be compromised if C++
> did what you want. Suggest you get that book and read it -- your
> respect for the language and its (seemingly random) decisions will go
> up a few notches.

I read Bjarne's original C++ book and found it nearly impenetrable. 
Of course, that was then and this is now, but he didn't seem to me to 
write in an overly clear style. Quite laden with technogrammar.

> >3. Why can't the compiler derive non-direct copy construction? eg;
> >class A { A(B &); } class B { B(C &}; } class C { C(const char *); }
> >A foo="Hello";
> 
> This was done to eliminate programming errors. The problem is to
> avoid surprising the programmer with bizarre chains of conversions
> that no human would ever think of on his own. For example, if someone
> accidentally typed this code:
> [a good example cut]
> No programmer would think that is intuitively obvious. Put it this
> way: most programmers find the automatic conversion/promotion
> "magical," and are somewhat afraid of them as a result. The idea of
> limiting the number of levels is to put a boundary on how much magic
> is allowed. We don't mind hiding mechanism from the C++ programmer,
> but we don't want to hide so much mechanism that no programmer could
> ever figure out what's going on.

Yeah, mechanism hiding is something anyone who used too many macros 
knows well about. It's virtually impossible to debug those.

You've convinced me on that part. I would say however that it would 
be a lot more possible if the compiler gave you debug info on what it 
had done. In fact, I'd say current compilers could do with a lot 
friendlier method of showing you what they'd done other than looking 
at the disassembly as currently :(

> HOWEVER there is an important conceptual difference as well. (I'll
> use the term "software development" to avoid using either term
> "design" or "implementation.")
> 
> In OO software development, the inheritance hierarchies are more
> fundamental and more foundational than the algorithms or data
> structures. That may seem strange to you, but if so, it's mainly
> because of the *way* you've tended to use inheritance in the past.
> 
> Here's why I say that: start with an OO system that has a base class
> 'Foo'. Base class Foo is abstract. In fact, it is PURE abstract: it
> has no data structures and algorithms -- it is a pure "interface" --
> all its methods are pure virtual. Next we create, over time, 20
> different derived classes, each of which has a different data
> structure and algorithm. The methods have a similar purpose, since
> their contracts are similar, but there is *zero* code reuse between
> these derived classes since all 20 inherit directly from 'Foo'.
> 
> So the algorithms and data structures are wholly replaceable. In
> fact, we intend to use all 20 different data structures and algorithms
> in the same program at the same time (not implying threading issues
> here; "same time" simply means "in the same "run" of the program, all
> 20 classes are used more-or-less continuously).
> 
> In OO systems that smell even remotely like this, the core algorithms
> and data structures are very secondary to the design, and in fact can
> be ignored during early design. During late design, someone will need
> to carefully select the best algorithms and data structures, but
> during early design all that matters is the inheritance structure, the
> method signatures in the base class 'Foo', and the "contracts" for
> those methods. If the contracts are set up right, then all 20 derived
> classes will be able to require-no-more, promise-no-less (AKA "proper
> inheritance"), and the derived classes can totally bury the algorithms
> and data structures.
> 
> It's almost like specifying an API then later implementing it. When
> you're specifying the API, all you care about is that the parameters
> and specifications ("contracts") are correct, complete, and
> consistent. If your API is clean enough, you actually *want* to be
> able to ignore the specific algorithm / data structure that will be
> used behind that API, since if you can bury that information behind
> the API, you know the algorithm / data structure can be scooped out
> and replaced if someone later comes up with a better one. The
> difference is that with OO, we have an API that has 20 different
> implementations and they're all pluggable, meaning the guy who is
> using the API never knows which implementation he's working with. 
> That forces us to "do the right thing" by designing our API (i.e.,
> methods in 'Foo', parameters, and especially contracts) in a way that
> all 20 derived classes are "proper" and that none of the 20 "leak" any
> of their private info (algorithms and data structures) to the user.
> 
> If you're still with me, then inheritance is more foundational than
> algorithms and/or data structures. Your past style of inheritance
> equated inheritance with data structure, after all, inheritance was
> just a way to group two chunks of software together. But now that you
> see the above dynamic-binding-intensive approach, perhaps you see that
> inheritance is an earlier lifecycle decision than algorithm. That's
> why I call the inheritance graph a critical (*the* critical) part of
> design. Get that right and the rest is replaceable.

It's odd you know because having thought about it, I do place 
algorithms I think important to performance in a neutral API 
container so they can be changed later on. For example, in that 
EuroFigher test bench, I had every input and output in the system 
given a unique name which a central device database looked up and 
mapped appropriately (with application of the correct calibration, 
metrification into units etc.). Now that central database did its 
searches using a binary search but I always had thought that if the 
item count exceeded about fifty, it'd be better to move to a hash 
table.

So, it's quite possible I was doing all along what your opinion is 
without realising it.

> >I have applied my skills to many projects: public, private and 
> >personal and I have not found my data-centric approach to have failed
> > yet. It has nothing to do with code maintainability nor much other
> >than efficiency - but that's why I use an impure OO for
> >maintainability - but if you rate superiority of a program based on
> >its excellence in functioning, my approach works very well. I
> >contrast with OO designed projects and quite simply, on average they
> >do not perform as well.
> 
> Re your last sentence, most OO software sucks because most OO
> designers suck.

Heh, that's agreed. However, can you see my point that when a newbie 
designs OO they tend to get it wrong? Hence my point that good OO 
isn't intuitive, and hence my point that there is something wrong 
with OO because a better system would be intuitive ie; complete 
newbie has a good chance of generating a good design?

> One other thing: I re-read what you wrote before and would like to
> separate it into two things. You said,
> 
> >>>I have never agreed with OO design as my university 
> >>>lecturers found out - I quite simply think it's wrong.
> 
> I agree with this part wholeheartedly. University lecturers typically
> don't know the first thing about how to actually get something done
> with OO. They have silly examples, and they tend to teach "purity" as
> if some customer actually cares if the code using their particular
> guidelines, rules, etc. Most of the time their guidelines are wrong,
> and even when the guidelines are right, they are, after all, just
> guidelines. The goal is still defined in business terms, not
> technical terms. E.g., the schedule, the cost, the functionality, the
> user acceptance testing, etc. have nothing to do with purity.
> 
> In fact, I disagree with most university lecturers at a deeper level
> since I really hate the one-size-fits-all approach. Too many
> technologists already know the answer before they hear the question.
> They know that Professor Know It All said we should use this technique
> and that approach, and even though they don't yet know anything about
> the problem, they already know how to solve it! I always find it's
> safer to listen before I speak, to ask before I answer. Having a
> solution that's looking for a problem is dangerous. Lecturers who
> (often implicitly) teach that approach to their students ultimately
> cause their students to have just one bullet in their gun. If the
> problem at hand *happens* to match the style, approach, and guidelines
> that the student believes/uses, everything works great; if not, the
> student will do a mediocre job.

I was very surprised to read you saying this. I had always thought 
most people involved with standardisation tend to come from academia 
and hence tend to have a view that they teach purity - the theory - 
which is completely divorced from commericial and indeed practical 
realities. Hence academia's strong dislike of C and indeed C++. They 
seem to prefer Modula-2 and Java respectively.

A little example is calculation of algorithm time based on operations 
eg; a load, a store etc. That may have worked ten years ago, but 
modern compilers have a frightening ability to rework your code and 
furthermore, in modern processors one load may cost up to 1000 times 
more than another load. Ok, so across the program it tends to average 
out, but nevertheless there is a growing chasm between what's good on 
paper and what's good in reality or, put another way, there is a 
growing chasm between the pure theory taught at universities and the 
realities of software engineering. Most companies will readily say 
universities produce poor quality graduates - indeed, there are 
people receiving first class degrees who *cannot* *program* a 
computer!

> >>>Computers
> >>>don't work naturally with objects - it's an ill-fit.
> >>>
> >>>What computers do do is work with data. If you base your design
> >>>entirely around data, you produce far superior programs. 
> 
> This is the part I was disagreeing about. You can see why, perhaps,
> in the example I gave above (the 'Foo' class with 20 derived classes
> each of which had its own distinct data structure and algorithm).

I'm afraid I don't. In your 20 derived classes, each is in fact its 
own autonomous data processor whose only commonality is that they 
share an API. The API is good for the programmer, but doesn't help 
the data processing one jot.

Hence my view that OO is good for organising source (intuitively it 
produces good source organisation) but poor for program design (ok, 
program algorithms in your terms).

> >>Be careful: you are painting yourself into a very narrow corner. 
> >>You may end up limiting your career as a result.
> >
> >Possibly, but I would doubt it. I may have some unique opinions on
> >this but what the customer cares about is (a) will it work and (b)
> >can we look after it well into the future. My case history strongly
> >supports both of these criteria, so a priori I'm on the right path.
> 
> Well, if you're pretty convinced you're already using the right
> overall approach, then you'll have no reason to question that overall
> approach and that means it will be a lot harder for you to learn new
> overall approaches. To me, the most important area to stay teachable
> is the big stuff. The first step in learning something new
> (especially something new at the paradigm level, i.e., a new way of
> approaching software as opposed to merely a new wrinkle that fits
> neatly within your current overall approach) is to a priori decide you
> might be on the wrong track. But that's up to you, of course.

I wouldn't sit down every night writing replies to you for four hours 
if I were not open to new ideas. You are pretty obviously someone I 
can learn off and indeed already have done so. Merely, what I am 
saying, is that on this one point you have not proved to me that I am 
wrong (yet), nor in fact does it seem I am having much success 
proving you are wrong to you.

> (BTW I learn something new at the paradigm level every year or so. I
> can't take it more often than that, but I really try to emotionally
> rip up *all* my preconceptions and start over every year or so. My
> buddy and I get together every year or so, write a paper to tell the
> world how great we were and what great things we'd done, then sit back
> and say to each other, "That was then, this is now. We're dinosaurs
> but we don't know it yet. The weather has probably already changed
> but we had our heads down and didn't notice. How has the weather
> changed? How do we need to adapt-or-die?" After keeping that up for
> a few minutes, we actually begin to believe it, and pretty soon we're
> looking scared and worried - like we really could miss the train
> that's leaving the station. Ultimately we'd end up reinventing
> ourselves, coming up with a new way of approaching projects,
> stretching, trying out radically new ideas, and very much not thinking
> we're a priori on the right path. It's always be painful and humbling,
> but it's made me who I am today. And who I am today is, of course, not
> good enough any more, so I'll have to change again! :-)

:-)

I would suggest you're being a little aggressive with yourself. If 
you took my approach (which is trying to stay at the entrance to as 
many alleyways as possible rather than go right up one to its 
extremity and then have to backtrack) then (a) no one would know you 
for coming up with new ideas and you'd be a lot less rich and well-
known and (b) possibly, you may be less rigid in your approach.

But no, I appreciate the point and fully agree with it. Only through 
embracing change and the constant opportunites to improve that it 
provides can we become our potential. Your method must be quite 
draining psychologically though.

> >>The point is that these benefits came as result of OO *design*, not
> >>as a result of programming-level issues.
> >
> >I'm sure OO design greatly improved the likely wasp's nest of 
> >spaghetti that existed in there previously. But I'm not seeing how OO
> > design is better than any other approach from this example - there
> >are many methods that could have been employed to achieve the same
> >result.
> 
> Two things:
> 
> 1. If what you said in the last sentence is true, where's the beef? 
> If these other approaches could do the same thing, why didn't they?
> 
> 2. I think you've missed the point I was making. The point was that
> this project used inheritance the way I'm proposing it should be used,
> and that's very different from the "inheritance is for reuse"
> approach. It's not about OO vs. non-OO. It's about how the two
> different styles of OO produce different results.

My point was that there are alternative methods of structuring and 
designing your code that have nothing to do with OO whatsoever. 
Furthermore, I believe what you call OO is in fact a composite of a 
number of different approaches many of which exist absolutely fine 
without having objects nor inheritence nor anything like it.

My fundamental point is that I think that you have integrated many 
beneficial and good programming practices into your internal 
conceptualisation of what OO is and means, and you are having 
difficulty separating them and treating them as what they are. I 
personally prefer to treat these things more seperately as I believe 
it offers me a great selection of tools from the toolbox as it were, 
but it's entirely a personal choice.

> >>>An example: take your typical novice with OO. Tell them the rules
> >>>and
> >>> look at what they design. Invariably, pure OO as designed against
> >>>the rules is as efficient as a one legged dog. 
> >>
> >>The way you have learned OO, yes, it will have performance problems.
> >>But the way I am proposing OO should be done, either it won't have
> >>performance problems at all, or if it does, those problems will be
> >>reparable.
> >
> >ie; You're bending OO to suit real-world needs, 
> 
> Not necessarily. I'm certainly not a believer in purity of any sort,
> and I'm certainly willing to eject *anybody's* definition of purity to
> achieve some greater goal. But I've also used OO *to* *do*
> performance tuning. That is, I've used various OO idioms and design
> approaches as a way to dynamically select the best algorithm for a
> particular task. The OO part of that is the selection, and is also
> the pluggability of new algorithms as they become known, and is also
> the fact that we can mix and match pieces of the problem using one
> algorithm and other pieces using another (as opposed to having one
> function 'f()' that has a single algorithm inside). I'm not saying
> none of this could be done without OO; I am rather saying that the
> performance tuning was, in some cases, within the spirit of what OO is
> all about.

Ah ha! - proof of my point above!

I am definitely thinking that if you and I were asked to sit down and 
design a solution to some software problem, our designs would be 
almost identical. How we would describe our own designs however would 
be completely different ie; we are arguing about perception.

> >which is precisely 
> >what I said experienced OO people do.
> 
> Certainly I'm both willing and have done so. But I don't seem to view
> that like you seem to view it. I don't see it as if something is
> lacking in OO. Rather I see it like I have a toolbox, and one of
> those tools says "OO," and I end up choosing the right combination of
> tools for the job. When I combine OO with non-OO, that doesn't bother
> me or indicate something is wrong with either OO or non-OO.

Right - then you must forgive me, because I had interpreted your 
repeated glowing testamonials of the efficacy of OO as you saying 
it's the best generalised solution to all programming problems.

> In contrast, it feels like what you're saying is, "If you can't do the
> *whole* thing using OO, something is wrong with OO." 

That is what I am saying - but *only* because OO is pushed as the 
currently best-known approach to writing programs. I have absolutely 
no problem with people saying OO is useful so long as it's not made 
out to be better than everything else.

> Here again, you seem to be saying that if OO isn't optimal for 100% of
> the solution, then something's wrong with it. I take the opposite
> tact, mainly because I am *not* a promoter for any given language or
> paradigm. In fact, I would be highly suspicious if someone (including
> you) claimed to have a technique that is optimal for 100% of the
> solution to any given problem, and especially if it was optimal for
> 100% of the solution of 100% of the problems. I simply do not believe
> that there exists any one-size-fits-all techniques, including OO,
> yours, or anybody else's.

What then do you feel is problematic with a data-centric approach? 
Why isn't it a better one-size-fits-all approach? Surely you would 
agree that if you base your design on quantities of data and the 
overheads of the media in which they reside, you naturally and 
intuitively produce a much more efficient design?

> >So, thoughts? I'm particularly interested in what you see as design
> >flaws 
> 
> Please compare and contrast with web-services. Obviously you're not
> married to XML like most web-services are, but they also have a
> concept of components / services through which data flows. Is there
> some similarity? Even at the conceptual level?

Good question.

The difference is in orientation. XML builds on top of the existing 
paradigm using existing software and its structure. Hence, the range 
of data it can process and how it processes it is quite limited 
(despite what its advocates might say).

What I propose goes the other way round - the programming is shaped 
by the needs of the data (rather than the other way round with XML). 
Of course, this needs a complete rewrite of all the software, but 
more on that later.

Fundamentally of course, XML is based around separating content from 
structure in order to achieve data portability. Now this is a 
laudable idea (and also one I think a pipedream) and partly of course 
my idea does the same. However, the fact I use much tinier data 
processors (ie; much finer granularity) and very different way of 
interfacing two formats of data I feel makes my solution far 
superior.

Of course, if they take XML much beyond what's already agreed, then I 
could have a problem on my hands. However, I think the same old 
propriatary data problems will raise their head and will subvert the 
possible potential. In the end, my method is completely compatible 
with XML, so I can always bind in XML facilities.

> [all very valid points about business]
> I'm not trying to discourage you - just trying to ask if you know what
> the pay-back really is. I'm also trying to remind you about how none
> of the front runners in OO survived, and ultimately it took a couple
> of decades before that paradigm took hold.

I completely agree with all these very valid points. But then I 
didn't explain my business model to you, only the technical model. 
The idea is to completely avoid business, because they won't buy it. 
The target for the first two years is actually slashdot readers. Let 
me explain:

Have you ever seen or used something that just impressed you with its 
quality? Have you ever really enjoyed programming for a certain 
language or operating system because it was so well designed?

In other words, I'm targeting the 20% of programmers or so who 
actually like programming and do it outside of work for fun. ie; a 
good majority of slashdot readers.

The runtime is obviously free and the SDK will mostly be free too ie; 
investors can't expect a return for the first two years. This is 
because in fact we're building a software base, without which no new 
paradigm stands a chance in hell.

We start making money when we put in the networking code sometime 
into the third year. This is where we start tying together all the 
advantages of this system and *leveraging* it. You get things like 
distributed computing, guaranteed email, integrated scheduling - all 
the stuff businesses like. More importantly, AFAIK only MS products 
do this kind of thing currently so my product would do it for Linux 
and Macs.

One we have a foothold into company intranets and a growing base of 
already skilled programmers, I think you're beginning to see how I'm 
intending to overcome all that which has killed languages, BeOS and 
plenty more. The biggest and most important is that programming for 
my proposed system should be the stuff of dreams (BeOS nearly got 
there, but in the end it didn't run on Windows - that's what killed 
it).

> >I've had Carl Sassenrath (he did much the OS for the 
> >Commodore Amiga) and Stuart Swales (did much of RISC-OS I mentioned
> >earlier) both agree it's probably right, but both wondered about
> >implementation. I should be especially interested in seeing what a
> >static OO based person things - neither Carl nor Stuart are static
> >code nor OO advocates hugely.
> 
> Please explain what you mean by "static code."

As in between Smalltalk and C++, or Objective C and C++, or even to 
extremes between interpreted and compiled languages. Generally I mean 
that Carl and Stuart from what I've observed tend to shift more 
processing into run-time (or what I call "dynamic code").

> >Furthermore, any advice about soliciting venture capital in Europe
> >would be useful (yes, I know it's like squeezing blood from a stone
> >here) - ever since the indigenous industry withered and died here,
> >it's been very hard to obtain capital for blue-sky projects without
> >the Americans buying them up. 
> 
> It's also hard here. There really has to be a decent business case.
> Honestly, if you're wanting to make money, you're better off using the
> Harvard Business School model: sell it first, *then* figure out how to
> deliver it. Building a better mousetrap and hoping somebody out there
> cares is an extremely risky business. Sometimes it works, but I think
> most of those are historical accidents - they happened to accidentally
> build the right thing at the right time.

I have to agree although the HBS method is unethical. Yeah, I know 
that sounds /so/ idealistic, but I am European and we live with the 
influence of Sartre!

> Seems to me you have two goals: stretch yourself via your idea (and in
> the process learn something new), and build a business that helps you
> keep the lights on, and possibly more. If the latter is your primary
> goal, seriously thinking about starting with a business case or even
> some sales attempts. If the former, then ignore everything else I've
> said and have a wonderful six months -- and may be, just may be,
> you'll hit the jackpot and actually sell the thing when you're done.

It was more that I'm sick and tired of fixing other people's 
grandiose mistakes only for them to show no appreciation and boot me 
out. I also don't much like constantly fighting people once their 
desperation phase (ie; when they're open to any new idea) has passed. 
I'd just prefer to do something that I personally enjoy doing and am 
interested in. If there were any blue-sky research happening in OS 
design here in Europe, then I'd be there. But here, most of the work 
is middleware, databases and web page design. Getting anything 
/interesting/ involves a big pay cut and job insecurity.

Now if I can get this project to pay me enough to live off, I'd 
actually forsake a high cash flow for a while. I'd prefer to be 
insecure and poor under my own management than someone else ;)

> >Anyway, any comments you may like to offer would be greatly 
> >appreciated. You've already earned yourself an acknowledgement in the
> > projects docs for helpful tips and suggestions.
> 
> Thanks for the acknowledgements.

You're welcome!

> Okay, here's another comment for you to think about (no need to
> reply): What specific application area will it be most suited for? 

Anything processing data. A game, for example, would be a very poor 
fit.

> Will it be most suited for embedded systems? handhelds? web servers?

None of those three. In fact, it's likely to require very significant 
overheads - it certainly uses a *lot* of processes and threads plus 
it uses a lot of caching, so memory use will be high.

However, I honestly don't know. I look at COM and I think my solution 
is likely to require less overhead. I won't know until I have working 
code to benchmark. I will say though I have gone to great lengths to 
optimise the system.

> apps servers? client-side programs? thin-client? Similarly, what
> specific industries are you seeing it fit best? Banking, insurance,
> transportation, etc.

Ultimately, I forsee it becoming the future standard for all 
operating systems and it linking every computer on the planet 
together ie; it replacing everything we know today as the de facto 
solution. Until something better comes along to replace it of course.

> May be these questions don't make sense, because may be I'm giving the
> wrong categories. E.g., may be the real question is whether it will
> be developing software development tools, database engines, or other
> "horizontal" apps, as opposed to the various vertical markets. 
> Whatever the correct categorization is, think about whether you know
> who would use it and for what (and please forgive me if I missed it
> above; it's very late). Ultimately if you have a reasonably crisp
> sense of who would buy it, you will be better able to target those
> programmers.

As mentioned above, it all starts with the programmers. No business 
will touch a technology without a hiring base - this is why MS has 
all those training courses.

> Programmers nowadays want highly customized tools. You
> mentioned GUI builders earlier. I personally despise those things,
> since they have dumbed down programming and turned it into something
> more akin to brick-laying, but I recognize that programmers nowadays
> really crave those easy-to-use tools. Many programmers nowadays don't
> seem to like writing code. They don't mind dragging the mouse on the
> screen, and clicking on a pull-down list of options, but many of them
> really don't want to actually type in code. I'm the opposite. Give
> me an Emacs and a command-line compiler, then stay out of my way. 
> Which really means I no longer "fit in" anywhere. Oh well, that's
> life I guess.

I'm writing this project using MSVC simply because it has good 
multithreading debug facilities whereas Unix seriously does not. I 
too don't much care for those graphical builder things, but you're 
right - I do know some managers who have learned how to throw stuff 
together using VBA which is of course built into every MS Office. 
Have you seen the development interface? It's hidden under the tools 
menu, but it *is* a free copy of VisualBasic. Almost identical in 
every facet. Why people buy VB nowadays is beyond me.

One point is the universities again - they teach you to use graphical 
tools which means you don't learn how it all works under the bonnet. 
Of course, a lot of industry megabucks goes in there to ensure that 
that will only get worse.

> I wandered - let me get back on track. The point was that programmers
> nowadays don't want generic tools that can be customized for their
> industry. They want specific tools that have already been customized
> for their industry. They want tools that are brain-dead easy to use,
> tools that don't require them to write much code. Will your vision be
> able to serve those sorts of programmers? If so, is the plan to do
> the customization with the VC money?

Initially, we use existing tools much as NT did first days with GCC. 
Then we slowly (and I mean after three to four years) add our own 
customised tools (eg; functional language). I think given our target 
market, that'll work ok.

> My not-so-hidden-agenda here is to help you begin to think like a
> "packaged software" company. Unlike custom software companies and
> end-user shops that build apps based on a spec from a well-defined
> "customer," the packaged-software companies spend *enormous* amounts
> of time and money doing market research before they write anything. 
> Here again it's the Harvard B-school approach: they're figuring out
> what will sell, then they build that. So think about that: what will
> sell? is there any latent frustration out there that would spur
> companies into changing their toolset and programmers? what is that
> frustration?

I'm thinking more why would a programmer choose to use my work. What 
would attract them to it, and keep them with it, and most importantly 
generate lots of freeware for it in their spare time. No code base = 
no killer app = dead product.

> viewed as adding any business value to the company." In other words,
> I'm just not seeing a great groundswell of frustration from technical
> people and/or first-line managers who are moaning in unison, "Our
> tools and/or languages suck; if we only had better tools and/or
> languages, life would be wonderful." I don't see why a new tool and a
> new language (and *especially* a new language within a new paradigm)
> would be viewed by the typical manager as a cost-saving measure. Like
> I said earlier, I'm not trying to throw a wet towel on your idea; more
> just being honest about what I do and don't see in the marketplace
> today.)

People in the western world often don't recognise how much perception 
influences their thoughts. The reason people don't think "why the 
hell won't windows let me do X" is the same reason why people don't 
think "I should live a healthy lifestyle because prevention is better 
than cure". It's a conceptual problem rooted in western culture, and 
particularly may I add in the US ie; an overly narrow perception of 
the causes of a problem or the role of an item - or, you could say 
over-reductionist and not enough considering of systemic problems 
(ie; our problem is not because MS haven't implemented feature X, 
it's because fundamentally Windows is not designed to do features 
like X).

I read an article once in Wired about how the Europeans are 
constantly going off and "reinventing the wheel" as they put it. I 
would instead say that a dissatisfaction with existing wheels meant 
we tried to improve on them by radically redesigning from the ground 
up rather than attempting to evolve existing ideas.

There's plenty plenty more on this problem of perception and asking 
the right questions by Fritjof Capra and others. The point is, I've 
deliberately designed my business model around the fact that you're 
completely right, no one will consider a radical departure from the 
norm until they have it sitting in front of them, demonstrating its 
vast superiority.

Of course, Microsoft will inevitably either try to buy the new idea 
out or generate a competing product. I'll cross that bridge if I come 
to it :)

Cheers,
Niall




From: "Marshall Cline" <[email protected]>
To: "'Niall Douglas'" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Thu, 1 Aug 2002 03:29:42 -0500

Niall Douglas wrote:
>On 30 Jul 2002 at 2:14, Marshall Cline wrote:
>
>>>Not being able to obtain these books easily (I live in Spain plus
>>>money is somewhat tight right now), I looked around the web for more
>>>on this. I specifically found what not to do when inheriting plus how
>>> deep subclassing usually results in code coupling increasing. Is
>>>that the general gist?
>>
>>That's a start. But coupling between derived and base class is a
>>relatively smaller problem than what I'm talking about. Typically
>>deep hierarchies end up requiring a lot of dynamic type-checking,
>>which boils down to an expensive style of coding, e.g., "if the class
>>of the object is derived from X, down-cast to X& and call method f();
>>else if it's derived from Y, down-cast to Y& and call g(); else if
>>...<etc>..." This happens when new public methods get added in a
>>derived class, which is rather common in deep hierarchies. The
>>if/else if/else if/else style of programming kills the flexibility and
>>extensibility we want to achieve, since when someone creates a new
>>derived class, they logically need to go through all those
>>if/else-if's and add another else-if. If they forget one, the program
>>goes into the "else" case, which is usually some sort of an error
>>message. I call that else-if-heimer's disease (pronounced like
>>"Alzheimer's" with emphasis on the word "forget").
>
>When you say dynamic type checking, you mean using typeid()? 

Perhaps, but there are lots of other ways to achieve it. There are two
general forms: the first is called a "capability query," where a
function 'f(Base* p)' asks the object at '*p' if it is capable of
performing some method 'm()'. The second is more like
'dynamic_cast<Derived*>(p)', where 'f(Base* p)' asks the object at '*p'
if its class is derived from 'Derived'.

In either case, the code ends up doing an if-then-else, and that
if-then-else is what causes the problems, since when someone creates a
new derived class, say 'Derived2', all those functions like 'f(Base* p)'
end up needing to get changed: to add another 'else if'.

>I had 
>thought that was to be avoided

Yes, that was my point earlier: deep hierarchies tend to result in
dynamic type checking, and that causes other problems.

>- in fact, any case where a base class 
>needs to know about its subclasses indicates you've got the 
>inheritance the wrong way round?

True.

However what I'm talking about is something different. I really need to
express the core idea more clearly. The idea of extensibility is
usually achieved by structuring your code so 99% (ideally 100%) of your
system is ignorant of the various derived classes. In particular,
pretend 'ABC' is an abstract base class with pure virtual methods but no
data and no code, and pretend 'Der1', 'Der2', ..., 'Der20' are concrete
derived classes that inherit directly from 'ABC'. The goal is for 99%
of the system to NEVER use the token Der1, Der2, ..., Der20 --- to pass
all these objects as 'f(ABC* p)' or 'g(ABC& x)'.

If the vast majority of the system, say 99% or 100% of the code is
ignorant about the names Der1, Der2, etc., then that 99% or 100% of the
system will be stable / unchanged if someone creates Der21 or Der22.

Now consider the case where one of the classes, say 'Der23', needs to
add a new (non-inherited) public method. Obviously the only way for
anyone to call that public method is to know the name 'Der23', since
they need to have parameters and/or locals of type 'Der23' or 'Der23*'
or 'Der23&'. If only a tiny part of the system, say 1% or less, knows
the name Der23, that's not too bad; but if a large portion of the system
knows that name (in order to call the "new public method"), then things
are starting to fall apart, since pretty soon someone will add 'Der24'
and add yet another "new public method" and will need to modify a large
portion of the system.

So "new public methods" cause problems when you use the above structure,
and deep hierarchies often result in derived classes defining new public
methods.

There are other problems with deep hierarchies, most especially the
reality that they often result in "improper inheritance," and that
causes unexpected violations of the base class's contract which
ultimately breaks the code of "the vast majority" of the system. This
proper-inheritance notion is the same as require-no-more,
promise-no-less, which you basically didn't like :-(

Big picture: Let's start over and ask, In addition to meeting the
requirements, what are we trying to achieve? Instead of saying
something vague like "reduce maintenance cost," I'll try to codify that
in terms of software stability: we want the bulk of the changes or
extensions to *not* require changes to the bulk of the system. A cutesy
way to say this is to eliminate the ripple effect. The point is to try
to build stuff in such a way that the bulk (say 99%) of the system is
stable when changes or extensions are made. The above is a partial
solution to this problem.


>>[stuff about bad class design deleted]
>>However since we used inheritance, we're pretty much stuck with
>>HashTable forever. The reason is that inheritance is a very "public"
>>thing -- it tells the world what the derived class *is*. In
>>particular, users throughout our million-line-of-code system are
>>passing Bags as HashTables, e.g., converting a Bag* to HashTable* or
>>Bag& to HashTable&. All these conversions will break if we change the
>>inheritance structure of Bag, meaning the ripple effect is much
>>higher.
>
>One of the things I look for when designing my class inheritances is 
>whether I could say, chop out one of the base classes and plug in a 
>similar but different one.

Interesting. That might be an artifact of the "other" approach to
inheritance, since that's almost exactly the opposite of what happens
with my code. Generally I design things so the base class is forever.
There is no benefit to unplugging it, and in fact it is extremely
unusual for it to be unpluggable since it codifies both the signatures
*and* the contracts that the various derived classes must abide by, and
plugging in another base class would almost always change those in some
way. But again, I emphasize that I never set up unpluggable base
classes *because* of the overall structure of my code, in particular the
structure where 99% of the system uses base-class pointers/references,
and that 99% is ignorant of any derived classes.

BTW another difference between the structure I'm talking about and the
"inheritance is for reuse" approach and/or the tall hierarchies approach
is the question, "Where is the code we're trying to reuse?" With
"inheritance is for reuse," the code we're trying to reuse is in the
base class, and it is typically the same in the tall hierarchies
approach. With the approach I'm talking about, the code we try to reuse
(that is, the code we want to *not* change) is the caller -- the 99% of
the system that is ignorant of the derived classes.

Said another way, the derived classes become application-specific
plug-ins, and the 99% can be thought of as glue code that holds them
together. Another interesting aspect of this is the call-graph
direction: it's the Hollywood model: "don't call us - we'll call you."
That's because, although the derived classes might occasionally call
some method in the 99%, the dominant call direction by far is for the
99% to call methods in the derived classes.

These last two paragraphs aren't describing a new model, but are trying
to give a couple of insights about the model I've been describing all
along.


>I'm just trying to think where I learned 
>that lesson - I think it was that cellular automata game I wrote to 
>refresh my C++ (http://www.nedprod.com/programs/Win32/Flow/) and I 
>realised it's best when the base class knows nothing at all about its 
>subclasses except that it has some and furthermore than subclasses 
>know as little as possible about what they inherit off (ie; they know 
>the inherited API obviously, but as little as possible about what 
>/provides/ the API). I think, if memory serves, it had to do with the 
>tools in the game. Of course, officially this is called reducing 
>coupling.
>
>>A derived class's methods are allowed to weaken requirements
>>(preconditions) and/or strengthen promises (postconditions), but never
>>the other way around. In other words, you are free to override a
>>method from a base class provided your override requires no more and
>>promises no less than is required/promised by the method in the base
>>class. If an override logically strengthens a
>>requirement/precondition, or if it logically weakens a promise, it is
>>"improper inheritance" and it will cause problems. In particular, it
>>will break user code, meaning it will break some portion of our
>>million-line app. Yuck.
>>
>>The problem with Set inheriting from Bag is Set weakens the
>>postcondition/promise of insert(Item). Bag::insert() promises that
>>size() *will* increase (i.e., the Item *will* get inserted), but
>>Set::insert() promises something weaker: size() *might* increase,
>>depending on whether contains(Item) returns true or false. Remember:
>>it's perfectly normal and acceptable to weaken a
>>precondition/requirement, but it is dastardly evil to strengthen a
>>postcondition/promise.
>
>This is quite ephemeral and subtle stuff. Correct application appears 
>to require considering a lot of variables.

I probably didn't describe it well since it's actually quite simple. In
fact, one of my concerns with most software is that it's not soft, and
in particular it has a large ripple effect from most any change. This
means a programmer has to understand how all the pieces fit together in
order to make most any change. In other words, the whole is bigger than
the sum of the parts.

The idea I described above means that the whole is *merely* the sum of
the parts. In other words, an average (read stupid) programmer can look
at a single derived class and its base class, and can, with total and
complete ignorance of how the rest of the system is structured, decide
unequivocally whether the derived class will break any existing code
that uses the base class. To use another metaphor, he can be embedded
in a huge forest, but he can know whether something new will break any
old thing by examining just one other leaf.

I rather like that idea since I have found the average programmer is,
well, average, and is typically unable or unwilling to understand the
whole. That means they screw up systems where the whole is bigger than
the sum of the parts -- they screw up systems where they must understand
a whole bunch of code in order to fully know whether a change will break
anything else.

I call this the "middle of the bell curve" problem. The idea is that
every company has a bell curve, and in the middle you've got this big
pile of average people, and unfortunately these average people can't
handle systems where the whole is bigger than the sum of the parts.
That means we end up relying on the hot-shots whenever we need to change
anything, and all the average guys sit around sucking their thumbs. I
think that's bad. It's bad for the hot-shots, since they can never do
anything interesting - they spend all their time fighting fires; it's
bad for the average people since they're under utilized; and it's bad
for the business since they're constantly in terror mode.

If we don't solve the middle-of-the-bell-curve problem, why have we
bothered with all these fancy tools, paradigms, etc.? In other words,
the hot-shots could *always* walk on water and do amazing things with
code, and they don't *need* OO or block-structured or GUI builders or
any other fancy thing. So if we don't solve the
middle-of-the-bell-curve problem, we might as well not have bothered
with all this OO stuff (or you could substitute "data centric stuff"),
since we really haven't changed anything: with or without our fancy
languages/techniques, the hot-shots can handle the complexity and the
average guys suck their thumbs.

Finding a way to utilize the middle of the bell curve is, in my mind, a
bulls eye for the industry as a whole. And I think the above approach
is a partial solution.


>>Please don't assume the solution is to make insert(Item) non-virtual.
>>That would be jumping from the frying pan into the fire, since then
>>Bag::insert() would get called on a Set object, and there actually
>>could be 2 or 3 or more copies of the same Item inside a Set object!! 
>>No, the real problem here isn't the override and it isn't the
>>virtualness of the method. The real problem here is that the
>>*semantics* of Set are not "substitutable for" those of Bag.
>
>This is quite a similar point to what you made two replies ago or so. 
>I'm not sure of the distinction between this example and the one 
>which you explained to me a few emails ago - ie; this example is 
>supposed to prove deep inheritance trees are evil but yet it would 
>seem you are proving the same point as before regarding bad 
>inheritance.

There were two new aspects here: an illustration of a deep hierarchy
that produced (as is typical) bad inheritance, and an exact codification
of exactly what I mean by "bad inheritance."


>Or are you saying that the deeper the tree, the much greater chance 
>it's a symptom of bad design?

Yes. And I'm also saying that there is a little algorithm that can
always determine if your inheritance is proper or not. It's the
require-no-more, promise-no-less test. It's equivalent to
substitutability, and ultimately it decides whether the derived class
will break any of the 99% of the user-code that uses base-class
pointers/references. (Of course the whole proper-inheritance thing only
has bite if you have base pointers/references referring to derived
objects.)


>>As before, aggregation would be perfectly safe and reasonable here:
>>Dictionary could have-a Set, could insert Association objects (which
>>would automatically be up-casted to Item&), and when it
>>accessed/removed those Items, Dictionary could down-cast them back to
>>Association&. The latter down-cast is ugly, but at least it is
>>logically safe -- Dictionary *knows* those Items actually are
>>Associations, since no other object anywhere can insert anything into
>>the Set.
>>
>>The message here is NOT that overrides are bad. The message here is
>>that tall hierarchies, particularly those built on the "inheritance is
>>for reuse" mantra, tend to result in improper inheritance, and
>>improper inheritance increases time, money, and risk, as well as
>>(sometimes) degrading performance.
>
>So, let me sum up: inheritance trees should be more horizontal than 
>vertical because in statically typed languages, that tends to be the 
>better design? Horizontal=composition, vertical=subclassing.

Yes, so long as you emphasize "tends." I emphasize "tends" and
deemphasize "should" because tall hierarchies are a yellow flag, not a
red flag. Improper inheritance is the red flag; tall hierarchies often
(not always) result in improper inheritance. The other yellow flag is
inheritance from a concrete class: that can cause performance problems
(see yesterday's description of Bag from HashTable), plus it can result
in accidental slicing via both the base class's copy ctor and assignment
operator (described a few days ago).


>No, I've got what you mean and I understand why. However, the point 
>is not different to what I understood a few days ago although I must 
>admit, Better = Horizontal > Vertical is a much easier rule of thumb 
>than all these past few days of discussion. You can use that rule in 
>your next book if you want :)

I've seen hierarchies with up to five levels, although all but the very
last were abstract base classes with almost no data or code. So again,
to me the real culprit is improper inheritance and/or inheritance from a
data structure. The former breaks user code and the later can cause
performance problems (creates a ripple effect when we try to improve
performance by changing to a different data structure).


>>* If Base::f() says it never throws an exception, the derived class
>>must never throw any exception of any type.
>
>That's an interesting one. I understand why already, I can infer it 
>from above. However, if the documentation of my subclass says it can 
>throw an exception and we're working with a framework which 
>exclusively uses my subclasses, 

(this is a structure I try to avoid; see above.)

>then all framework code will 
>ultimately have my code calling it ie; bottom of the call stack will 
>always be my code. Hence, in this situation, it is surely alright to 
>throw that exception?

Yes, because the 99% now knows about the derived class. Again, I try to
avoid this structure, but the big-picture goal is to keep the 99% stable
in the face of changes.


>I say this because my data streams project can throw a TException in 
>any code at any point in time (and there are explicit and loud 
>warnings in the documentation to this regard). Of course, one runs 
>into problems if the base class when calling its virtual methods does 
>not account for the possibility of an exception.

Bingo.


>>>Hence, that TSortedList should now derive off QGList which doesn't
>>>have the append and prepend methods so I can safely ensure it does
>>>what its parent does.
>>
>>What you really ought to do is check the *semantics* of QGList's
>>methods, in particular, read the preconditions and postconditions for
>>those methods. (I put these in the .h file for easy access, then use
>>a tool to copy them into HTML files; Qt seems to put them in separate
>>documentation files; either way is fine as long as they exist
>>somewhere.) Inheritance is an option if and only if *every* method of
>>TSortedList can abide by the corresponding preconditions and
>>postconditions in QGList.
>
>Actually, to tell you the truth, I had already looked through QList 
>and QGList to ensure my altering of TSortedList wouldn't cause 
>problems - those disabled methods weren't called internally within 
>QGList, so I was fairly safe the list would always remain sorted.

Even though QList and QGList don't call the disabled methods, you're not
as safe as you think. Pretend for the moment you're part of a larger
team of programmers. Someone out there could innocently pass a
TSortedList as a QList& or QList*, and then the called function could
(via the QList& or QList*) access the removed methods and screw things
up. For example, they could cause the list's contents to become
unsorted, and that could screw up TSortedList's binary search
algorithms. The only way to insulate yourself from this is to use has-a
or private/protected inheritance.


>Actually, those embedded systems are becoming disgustingly powerful - 
>that GPS receiver was a 80Mhz 32 bit processor with 16Mb of RAM and 
>optionally 64Mb of ROM. On that, you can write your applications in 
>Visual Basic and it'll actually go. Of course, that's what Windows CE 
>is all about.
>
>Embedded systems programming as we knew it is dying out. When 
>desktops went all powerful, a lot of us assembler guys went into tiny 
>systems but now they've gone all powerful, it's a rapidly shrinking 
>market. The writing is definitely on the wall - move onto OO and C++ 
>and such or else become unemployable.

You may have already worked with hand-held systems, but if not, they
might be the last bastion of tight, high-tech coding. Particularly
hand-held systems targeted at the consumer market, since that usually
means the company wants to squeeze the unit cost and extend the battery
life. In those cases, they worry about everything. Wasting memory
means the thing needs more RAM or flash, and that increases unit cost
and reduces battery life. Similarly wasting CPU cycles burns the
battery up pretty fast. So in the end they want it very small and very
fast, and that makes it challenging/fun.


>>And after all these questions are answered, somewhere down on the list
>>are things like the relative "cleanliness" of the language. Are the
>>constructs orthogonal? Is there appropriate symmetry? Are there
>>kludges in the syntax? Those things will effect the cost of the
>>software some, to be sure, but they aren't life-and-death issues like
>>whether we can buy/rent programmers or whether we can buy/license good
>>tools. I have a client that is using (foolishly) a really clean,
>>elegant language that almost nobody uses. Most programmers who use
>>that language for more than a month absolutely love it. But the
>>client can't buy or rent programmers or tools to save its life, and
>>its multi-million dollar project is in jeopardy as a result.
>
>What's the language?

Limbo. It's hosted within Inferno, an OS that was originally by Lucent,
but was sold to a UK company named Vita Nuova.

Limbo was designed by Dennis Ritchie and some other really smart folks
(BTW I had a chance to talk to Dennis on the phone as a result of this
engagement), and everyone involved gives it glowing reviews. But like I
said, my client is having a hard time finding people to work with it
since there simply aren't that many Limbo programmers out there.

Somewhat interesting approach. It's hosted via a virtual machine, and
it's compiled into a byte-code of sorts, but it's very different from
the stack-machine approach used by Java. It's much closer to a
register-machine, so the source code "a = b + c" compiles into one
instruction (pretend they're all of type 'int', which uses the 'w'
suffix for 'word'):

addw b, c, a // adds b+c, storing result into a

The corresponding Java instructions would be something like this:

iload b // pushes b
iload c // pushes c
iadd // pops c then b, adds, pushes the sum
istore a // pops the sum, stores into a

There are two benefits to the Limbo byte-code scheme: it tends to be
more compact, on average, and it's much closer to the underlying
hardware instructions so a JIT compiler is much smaller, faster, uses
less memory, and is easier to write. E.g., a Java JIT has to convert
all these stack instructions to a typical machine-code add, and that
transformation has to happen on the fly, whereas Limbo does most of that
transformation at compile-time.

It also has some very interesting data transmission techniques, based on
CAR Hoare's CSP (communicating sequential processes) model. By tying
their synchronization and data transmission technique to CSP, they
instantly know all sorts of facts that academics have proven about their
language/environment. For example, Hoare showed that CSP can be used to
build any other synchronization primitive (such as semaphores, or
Java-like monitors, or anything else), and a whole bunch of academics
created all sorts of reader/writer scenarios that can be exploited by
the language.

The Limbo implementation of CSP is via channels. You create a channel
in your code, then one thread reads from the channel and another thread
writes to it. Channels aren't normally tied to files, but they can be.
Pretty slick stuff, and somewhat related to what you're doing. For
example, you can have a channel of 'int', or a channel of 'Xyz' where
the latter is a struct that contains all sorts of stuff, or anything in
between.

The language takes some getting used to, primarily because it, unlike C
or C++, has *no* back door to let you do things that are nasty. E.g.,
there are no unchecked pointer casts, there is nothing corresponding to
a 'void*' type, function pointers have to specify all parameters
exactly, there is no is-a conversion or any other way to get a Foo
pointer to point at a Bar object, etc. Obviously the byte-code level
(called "Dis") lets you do these things, but Limbo itself tries to
protect idiots from being idiotic. (As you might guess, I had to write
some Dis code for some things. That was fine, of course, but it was
somewhat bizarre seeing that Limbo offered no alternative.)

You might want to read their articles about CSP. See www.vitanuova.com.
Also check out Lucent's web site.


>>Yes, C is closer to the machine, since its mantra is "no hidden
>>mechanism." C++ *strongly* rejects the no-hidden-mechanism mantra,
>>since its goal is ultimately to hide mechanism - to let the programmer
>>program in the language of the *problem* rather than in the language
>>of the *machine*. The C++ mantra is "pay for it if and only if you
>>use it." This means that C++ code can be just as efficient as C code,
>>though that is sometimes a challenge, but it also means that C++ code
>>can be written and understood at a higher level than C code -- C++
>>code can be more expressive -- you can get more done with less effort.
>> Of course it is very hard to achieve *both* those benefits (more done
>>with less effort, just as efficient as C) in the same piece of code,
>>but they are generally achievable given a shift in emphasis from
>>programming to design (using my lingo for "design"). In other words,
>>OO software should usually put proportionally more effort into design
>>than non-OO software, and should have a corresponding reduction in the
>>coding effort. If you're careful, you can have dramatic improvements
>>in long-term costs, yet keep the short-term costs the same or better
>>as non-OO.
>
>That's an interesting point - that as the languages evolve, more time 
>proportionally needs to go into design.

Not sure if it's the evolution of the language. I think it's the added
goals of a typical OO project. A typical OO project has all the
functional and non-functional goals of a typical non-OO project, but the
OO project often gets additional non-functional goals. For example, an
OO project is somehow supposed to be easier to adapt, generate more
reuse, lower maintenance cost, that sort of thing. So I think the added
design-time comes because we end up trying to do more things at once --
it's harder to achieve 3 goals than 1.

I think the extra design would have achieved at least some of these
things in non-OO languages as well, but I also believe (perhaps unlike
you) that an OO language makes it easier. Here's why I believe that: I
can achieve everything in straight C that I can in C++, since I can
always manually simulate a virtual-pointer / virtual-table mechanism,
hand-mangle function names to simulate function or operator overloading,
manually create the state transition tables used by exception handling,
etc. However there would be a lot of grunt bookkeeping code, and that
would eventually cloud the ultimate goal. Even if I chose a simplified
set of features to simulate, it would still add chaff to the code and
that would make it harder to work with. One real-world example of this
is X (AKA the X Windows System). X was written in C using OO design,
and they had all sorts of macros to handle inheritance and all the rest,
but eventually they lost a grip on the system because it had too much
bookkeeping crap.

My point in all of this is simply this: if you have only one goal, you
don't need much design time at all. If you have two goals, you need a
little more design time. If you have 20 goals you want to achieve at
the same time, you probably need a lot of design time (no matter what
language you are using). I think that might be the reason typical OO
projects have proportionally more design-time and less code-time.


>>People who don't understand good OO design (my definition, again;
>>sorry) tend to screw things up worse with OO than with non-OO, since
>>at least with non-OO they don't *try* to achieve so many things at
>>once -- they just try to get the thing running correctly and
>>efficiently with hopefully a low maintenance cost. In OO, they try to
>>use OO design (my defn) in an effort to achieve all those *plus* new
>>constraints, such as a dramatic increase in software stability, a
>>dramatic reduction in long-term costs, etc. But unfortunately, after
>>they spend more time/money on design, they have a mediocre design at
>>best, and that mediocre design means they *also* have to pay at least
>>as much time/money on the coding stage. They end up with the worst of
>>both worlds. Yuck.
>>
>>The difference, of course, is how good they are at OO design (using my
>>defn).
>
>I would personally say it's about how good they are at *design* full 
>stop period. I still hold that it's unimportant whether you use OO or 
>not - it's merely one of the tools in the toolbox and its merit of 
>use entirely depends on the situation.

I think you just weakened your argument about OO being non-intuitive.
Below you said:

>... can you see my point that when a newbie 
>designs OO they tend to get it wrong? Hence my point that good OO 
>isn't intuitive, and hence my point that there is something wrong 
>with OO because a better system would be intuitive ie; complete 
>newbie has a good chance of generating a good design?

The argument you're making seems reasonable: when a newbie designs OO he
gets it wrong, therefore good OO isn't intuitive, therefore there is
something wrong with OO.

However, once you admit that these people are no good at design full
stop period, the two "therefore"s go away -- the fact that they're no
good at design full stop period provides an alternative explanation for
why they get OO designs wrong.


>>It shouldn't. Try this code and see if it causes any errors:
>
>Actually, I tried:
>--- cut ---
>class BaseString {
>public:
> BaseString(const char* s);
> BaseString &operator=(const char *);
>};
>
>class DerivedString : public BaseString {
>public:
> DerivedString();
> DerivedString(const BaseString &s);
> DerivedString(const char* s);
> DerivedString &operator=(const char *);
>};
>
>int main()
>{
> DerivedString foo("foofoo") ;
> foo = "Hello world";
> return 0;
>}
>--- cut ---
>
>>I think that properly represents the problem as you stated it:
>> >>>TQString foo;
>> >>>foo="Hello world";
>> >>>
>> >>>Now TQString is a subclass of QString, and both have const char *
>> >>>ctors. The compiler will refuse to compile the above code because
>> >>>there are two methods of resolving it. "
>>
>>Let me know if the above compiles correctly. (It won't link, of
>>course, without an appropriate definition for the various ctors, but
>>it ought to compile as-is.)
>>
>>If the above *does* compile as-is, let's try to figure out why you
>>were frustrated with the behavior of TQString.
>
>Yes, it compiles fine. And no, I'm not sure why it does when TQString 
>does especially when I've faithfully replicated the constructor 
>hierarchy above.

Are any of the ctors in either TQString or QString listed as "explicit"?

Are there explicit copy ctors in either/both?

Note that your 'DerivedString' has a ctor that takes a (const
BaseString&), which is not a copy ctor. Is that intentional?

It seems very strange to me that QString would have an operator= that
takes a (const char*), but not one that takes a (const QString&). If it
really takes both, you might want to add them both.

Basically I'm curious and frustrated that I don't understand this one.
If you're willing, keep adding signatures from QString/TQString to
BaseString/DerivedString until the latter breaks. I'd be thrilled if
you can chase this one down, but I'll obviously understand if you can't.
(I *hate* irrational errors, because I'm always afraid I've missed
something else. Like your "template<class type>" bug, adding a pointer
cast made the error go away, but I don't think either of us were
comfortable until we found the real culprit.)


>>>> [overloading based on return type]
>>>>Another C++ idiom lets you do just that. I'll have to show that one
>>>>to you when I have more time. Ask if you're interested.
>>>
>>>Is that like this:
>>>bool node(TQString &dest, u32 idx)
>>>bool node(TKNamespaceNodeRef &ref, u32 idx)
>>>...
>>
>>Nope, I'm talking about actually calling different functions for the
>>following cases:
>>
>> int i = foo(...);
>> char c = foo(...);
>> float f = foo(...);
>> double d = foo(...);
>> String s = foo(...);
>
>Ok, I'm interested now. You can point me at a webpage if one exists.

No prob. To make sure we're on the same page, let's be explicit that
all the 'foo()' functions take the same parameter list, say an 'int' and
a 'double', so the only difference is their return types. I'll first
rewrite the "user code" using these parameters:

void sample(int a, double b)
{
int i = foo(a, b);
char c = foo(a, b);
float f = foo(a, b);
double d = foo(a, b);
String s = foo(a, b);
}

The rules of the game are simple: if we can get a totally separate
function to get called for each line above, we win.

The solution is trivial:

class foo {
public:
foo(int a, double b) : a_(a), b_(b) { }
operator int() const { ... }
operator char() const { ... }
operator float() const { ... }
operator double() const { ... }
operator String() const { ... }
private:
int a_;
double b_;
};

QED


>>>1. Why didn't C++ have separated support for code reuse and subtyping
>>> (like Smalltalk)?
>>[explanation chopped]
>>So if C++ wanted to be like Smalltalk, it could do what you want. But
>>given that C++ wants compile-time type-safety, it can't do what you
>>want.
>
>I personally would probably have had it use static typing when it 
>could, but when the compiler didn't know it would complain unless you 
>added a modifier to say it was a dynamic cast - then the check gets 
>delayed till run time. As it happens, surely that's happened anyway 
>(albeit relatively recently) with dynamic_cast<>().
>
>My point is, it could have been made possible to utilise the best of 
>both worlds but with a bias toward static typing.

I think your goal is admirable. However if you think a little deeper
about how this would actually get implemented, you would see it would
cause C++ to run much slower than the worst Smalltalk implementation,
and to generate huge piles of code for even trivial functions. E.g.,
consider:

void foo(QString& a, QString& b)
{
a = "xyz" + b;
}

Pretend QString's has a typical 'operator+' that is a non-member
function (possibly a 'friend' of QString). It needs to be a non-member
function to make the above legal. Pretend the signature of this
'operator+' is typical:

QString operator+ (const QString& x, const QString& y);

Thus the 'foo()' function simply promotes "xyz" to QString (via a
QString ctor), calls the operator+ function, uses QString's assignment
operator to copy the result, then destructs the temporary QString.

However if your relaxed rules above let someone pass things that are not
a QString (or one of its derived classes) for 'a' and/or 'b', things are
much worse. (And, unfortunately, if your relaxed rules do not allow
this, then I don't think you're getting much if any advantage to your
relaxed rules.)

In particular, if 'a' and/or 'b' might not be QString objects, the
compiler would need to generate code that checked, at run-time, if there
exists any 'operator+' that can take a 'char*' and whatever is the type
of 'a' (which it won't know until run-time). Not finding one, it would
search for valid pointer conversions on the left, e.g., 'const char*',
'void*', 'const void*'. Not finding any of those, it would also search
for any 'operator+' that takes the type of 'b' on the right. Finally,
if we assume 'b' actually is a QString, it would find a match since it
could promote the type of 'b' from 'QString&' to 'const QString&'
(that's called a cv-conversion).

However it's not done yet. To make the only candidate 'operator+' work,
it has to try to convert the left-hand parameter from 'char*' to
whatever is on the left-side of the 'operator+' (which it would discover
at run-time to be 'const QString&'). Eventually it will discover this
can be done in three distinct steps: promote the 'char*' to 'const
char*', call the QString ctor that takes a 'const char*', then bind a
'const QString&' to the temporary QString object. Now if finally has
enough information to call 'operator+'.

But it's still not done, since it then has to perform even more steps
searching for an appropriate assignment operator. (Etc., etc.)

BTW, I've greatly simplified the actual process for function and
operator overloading. In reality, the compiler (and, under your scheme,
the run-time system) is required to find *all* candidate operators that
can possibly match the left-hand-side, and all that can possibly match
the right-hand-side, then union them and get exactly one final match
(there's some paring down as well; I don't remember right now). The
point is that it's nasty hard, and will require a nasty amount of code.


>>>2. Why don't return types determine overload?
>>
>>Because things like this would be ambiguous:
>>
>> int f();
>> float f();
>> char f();
>>
>> int main()
>> {
>> f();
>> ...
>> }
>
>That's easy - if there's an f() returning void, it's the correct one 
>to call. If there isn't, it's a compile error - you'd need (char) f() 
>or something to say which to call.

C++ is not D = we can't add rules that cause legal C programs to
generate compile errors unless there is a compelling reason to do so.

What would happen with this:

void foo(char* dest, const char* src)
{
strcpy(dest, src);
}

Or even the simple hello-world from K&R:

int main()
{
printf("Hello world!\n");
return 0;
}

Would those generate an error message ("No version of
'strcpy()'/'printf()' returns 'void'")?
* If they would cause an error, we break too much C.
* If they don't cause an error, we jump from the frying pan into the
fire: if someone later on created a version of those functions that
overloaded by return type, all those calls would break because suddenly
they'd all start generating error messages ("missing return-type cast"
or something like that). In other words, the programmer would have to
go back through and cast the return type, e.g., (int)printf(...) or
(char*)strcpy(...).

Adding a return-type-overloaded function wouldn't *always* cause an
error message, since sometimes it would be worse - it would silently
change the meaning of the above code. E.g., if someone created a 'void'
version of printf() or strcpy(), the above code would silently change
meaning from (int)printf(const char*,...) to a totally different
function: (void)printf(const char*,...).


>>Worse, if the three 'f()' functions were compiled in different
>>compilation units on different days of the week, the compiler might
>>not even know about the overloads and it not notice that the call is
>>ambiguous.
>
>That can happen anyway surely if you're talking different scopes?

I don't think so. If two functions are in different classes, there's no
way to accidentally forget to include the right header since you need to
call those functions via an object or a class-name. On the other hand,
if they're defined in different namespace scopes, then again I don't
think you can call them without qualification. Here's a way it might
work the way you suggest: if I'm compiling function f() within namespace
xyz, and if my f() calls g() without qualification, that could mean a
g() within xyz or a g() at filescope (that is, outside any namespace).
If someone forget to include the header that declared xyz's g(), the
compiler would make the wrong choice.

But that seems like a pretty obscure example.


>>There's an interesting example in Bjarne's "Design and Evolution of
>>C++" that shows how type safety would commonly be compromised if C++
>>did what you want. Suggest you get that book and read it -- your
>>respect for the language and its (seemingly random) decisions will go
>>up a few notches.
>
>I read Bjarne's original C++ book and found it nearly impenetrable. 

His writing is hard to read by anyone.


>Of course, that was then and this is now, but he didn't seem to me to 
>write in an overly clear style. Quite laden with technogrammar.

D&E (as the book is affectionally called) is a valuable resource for
someone like you, since it explains why things are the way they are.
It's probably not as hard to read as The C++ Programming Language since
it's really a narrative or story of how Bjarne made his decisions and
why. But even if it is hard to read, you still might like it.
(Obviously if you have only one book to buy, buy mine, not his! :-)
(Actually I get only a buck per book so I really have almost no
incentive to hawk the thing.)


>>>>>Computers
>>>>>don't work naturally with objects - it's an ill-fit.
>>>>>
>>>>>What computers do do is work with data. If you base your design
>>>>>entirely around data, you produce far superior programs. 
>>
>>This is the part I was disagreeing about. You can see why, perhaps,
>>in the example I gave above (the 'Foo' class with 20 derived classes
>>each of which had its own distinct data structure and algorithm).
>
>I'm afraid I don't. In your 20 derived classes, each is in fact its 
>own autonomous data processor whose only commonality is that they 
>share an API. The API is good for the programmer, but doesn't help 
>the data processing one jot.

I have no idea what I was thinking above - the logic seems to totally
escape me. Perhaps I was referring to your last sentence only, that is,
to base your design totally around data. Yea, that's what I was
thinking. Okay, I think I can explain it.

In my base class 'Foo', the design of the system was based around 'Foo'
itself and the API specified by Foo. 99% of the system used 'Foo&' or
'Foo*', and only a small percent of the code actually knew anything
about the data, since the data was held in the derived classes and 99%
of the system was ignorant of those. In fact, there are 20 *different*
data structures, one each in the 20 derived classes, and "99% of the
system" is ignorant of all 20.

The point is the vast majority of the code (say 99%) doesn't have the
slightest clue about the data. To me, that means the code was organized
*not* around the data. The benefit of this is pluggability,
extensibility, and flexibility, since one can add or change a derived
class without breaking any of the 99%.

I'm still not sure that addresses what you were saying, but at least I
understand what I was trying to say last night.


>Hence my view that OO is good for organising source (intuitively it 
>produces good source organisation) but poor for program design (ok, 
>program algorithms in your terms).

I think OO has one bullet in its gun: it is good for achieving
non-functional goals, like extensibility, flexibility, etc. If you are
*very* careful, you can achieve those non-functional goals without
sacrificing other non-functionals, such as speed. I think if someone
has a program with no extensibility and no flexibility goals, then OO
adds little genuine value. (Today's hottest tools tend to be built
around C++ and Java, so there is a peripheral benefit to using one of
those languages even if you don't want flexibility / extensibility. But
that peripheral benefit has nothing to do with OO per se; it's merely an
accident of history that today's tool vendors attach their best stuff to
those languages.)


>>>>The point is that these benefits came as result of OO *design*, not
>>>>as a result of programming-level issues.
>>>
>>>I'm sure OO design greatly improved the likely wasp's nest of 
>>>spaghetti that existed in there previously. But I'm not seeing how OO
>>> design is better than any other approach from this example - there
>>>are many methods that could have been employed to achieve the same
>>>result.
>>
>>Two things:
>>
>>1. If what you said in the last sentence is true, where's the beef? 
>>If these other approaches could do the same thing, why didn't they?
>>
>>2. I think you've missed the point I was making. The point was that
>>this project used inheritance the way I'm proposing it should be used,
>>and that's very different from the "inheritance is for reuse"
>>approach. It's not about OO vs. non-OO. It's about how the two
>>different styles of OO produce different results.
>
>My point was that there are alternative methods of structuring and 
>designing your code that have nothing to do with OO whatsoever. 
>Furthermore, I believe what you call OO is in fact a composite of a 
>number of different approaches many of which exist absolutely fine 
>without having objects nor inheritence nor anything like it.
>
>My fundamental point is that I think that you have integrated many 
>beneficial and good programming practices into your internal 
>conceptualisation of what OO is and means, and you are having 
>difficulty separating them and treating them as what they are. I 
>personally prefer to treat these things more seperately as I believe 
>it offers me a great selection of tools from the toolbox as it were, 
>but it's entirely a personal choice.

You're probably right. I'm not an advocate for any given style of
programming, since any advocate for anything ends up being a
one-trick-pony, and they can only be radically successful if their
particular "trick" happens to be a really good fit for the project du
jour. Instead I try to advocate success over all, and that means
intentionally using whatever styles help achieve that success.


>>Here again, you seem to be saying that if OO isn't optimal for 100% of
>>the solution, then something's wrong with it. I take the opposite
>>tact, mainly because I am *not* a promoter for any given language or
>>paradigm. In fact, I would be highly suspicious if someone (including
>>you) claimed to have a technique that is optimal for 100% of the
>>solution to any given problem, and especially if it was optimal for
>>100% of the solution of 100% of the problems. I simply do not believe
>>that there exists any one-size-fits-all techniques, including OO,
>>yours, or anybody else's.
>
>What then do you feel is problematic with a data-centric approach? 

That's easy: one size does not fit all. There's nothing "problematic"
about it, but it is a style, and therefore it will be a good fit for
some problems and a not-so-good fit for others.


>Why isn't it a better one-size-fits-all approach? 

Because there is no one-size-fits-all approach! :-)


>Surely you would 
>agree that if you base your design on quantities of data and the 
>overheads of the media in which they reside, you naturally and 
>intuitively produce a much more efficient design?

Even if what you're saying is true, "a much more efficient design" might
not the top priority on "this" project. All I'm saying is: I prefer to
start with the goals, *then* decide which technologies to use. Anyone
who comes in talking, who already knows which technologies should be
used before understanding the goals, is foolish in my book.

I wouldn't want to assume your data-oriented approach is the answer any
more than I would want to assume OO is the answer. First tell me what
the question is, *THEN* I'll come up with the "most appropriate" answer.

(BTW I think technologists typically get off track when they call one
technology "better" than another. I think they should use words like
"more appropriate for my particular project," since "better" seems to
imply "better in all projects in all industries for all time." I don't
think you said that; it was just an off-the-wall comment.)


>>>So, thoughts? I'm particularly interested in what you see as design
>>>flaws 
>>
>>Please compare and contrast with web-services. Obviously you're not
>>married to XML like most web-services are, but they also have a
>>concept of components / services through which data flows. Is there
>>some similarity? Even at the conceptual level?
>
>Good question.
>
>The difference is in orientation. XML builds on top of the existing 
>paradigm using existing software and its structure. Hence, the range 
>of data it can process and how it processes it is quite limited 
>(despite what its advocates might say).
>
>What I propose goes the other way round - the programming is shaped 
>by the needs of the data (rather than the other way round with XML). 
>Of course, this needs a complete rewrite of all the software, but 
>more on that later.
>
>Fundamentally of course, XML is based around separating content from 
>structure in order to achieve data portability. Now this is a 
>laudable idea (and also one I think a pipedream) and partly of course 
>my idea does the same. However, the fact I use much tinier data 
>processors (ie; much finer granularity) and very different way of 
>interfacing two formats of data I feel makes my solution far 
>superior.
>
>Of course, if they take XML much beyond what's already agreed, then I 
>could have a problem on my hands. However, I think the same old 
>propriatary data problems will raise their head and will subvert the 
>possible potential. In the end, my method is completely compatible 
>with XML, so I can always bind in XML facilities.

You might want to think about this as you go forward. XML's limitations
are the fact that it's text based (speed, perhaps some limitations in
its flexibility), and, paradoxically, it is too good at being
self-describing and therefore there are some security concerns. (If
you're aware of that second point, skip this: if an XML glob has a tag
somewhere that says <CreditCard>....</CreditCard>, then hackers know
just where to look to get the info they want. If it wasn't so
self-describing, it would be harder to hack. People are very aware of
this problem and are working on it. Fortunately (or unfortunately??)
the solution is trivial: encrypt all XML blobs that pass across a
network.)

The point is that XML has some limitations, but it seems like people are
going to be able to get 90% of what they want via XML, and then 95%,
then 97%, etc. That progression makes your stuff less compelling.


>>[all very valid points about business]
>>I'm not trying to discourage you - just trying to ask if you know what
>>the pay-back really is. I'm also trying to remind you about how none
>>of the front runners in OO survived, and ultimately it took a couple
>>of decades before that paradigm took hold.
>
>I completely agree with all these very valid points. But then I 
>didn't explain my business model to you, only the technical model. 
>The idea is to completely avoid business, because they won't buy it. 
>The target for the first two years is actually slashdot readers. Let 
>me explain:
>
>Have you ever seen or used something that just impressed you with its 
>quality? Have you ever really enjoyed programming for a certain 
>language or operating system because it was so well designed?
>
>In other words, I'm targeting the 20% of programmers or so who 
>actually like programming and do it outside of work for fun. ie; a 
>good majority of slashdot readers.
>
>The runtime is obviously free and the SDK will mostly be free too ie; 
>investors can't expect a return for the first two years. This is 
>because in fact we're building a software base, without which no new 
>paradigm stands a chance in hell.
>
>We start making money when we put in the networking code sometime 
>into the third year. 

That's a *very* hard sell to a VC guy. They have lots of people
claiming to deliver a 100% or 1000% return in the first year. To admit
you're a money pit that won't even generate any revenue for 3 years (and
won't probably generate profit for many more years) will be a *very*
hard sell.

Think about using Cygnus's business model. Cygnus was Michael Tiemann's
old company (I think they were acquired by Red Hat). They had a similar
goal as yours, only they had much more popular tools, e.g., the GNU C
and C++ compilers, etc., etc. Michael wrote the first version of g++,
then rms (Stallman) got back into the mix and they worked together on a
brand new GCC that combined C, C++, Objective C, Java, FORTRAN, and
probably Swahili into the same compiler. The point is that the GNU
tools are free, but Cygnus got revenue by charging corporations for
support. Big companies in the US are afraid of free software. They
want *somebody* they can call and say, "I need you to fix this bug NOW!"
So Michael offered them a one-day turn-around on bugs (or something like
that) for $20,000/year (or something like that). He told them he'd
distribute the bug-fix to everyone free of charge, but they didn't care:
they wanted to know THEIR programmers wouldn't get hung up on bugs, so
it made business sense.


>>Will it be most suited for embedded systems? handhelds? web servers?
>
>None of those three. In fact, it's likely to require very significant 
>overheads - it certainly uses a *lot* of processes and threads plus 
>it uses a lot of caching, so memory use will be high.
>
>However, I honestly don't know. I look at COM and I think my solution 
>is likely to require less overhead. I won't know until I have working 
>code to benchmark. I will say though I have gone to great lengths to 
>optimise the system.

Think about that - it might be worthwhile to create a few "sample apps"
and a few "sample app programmers," that way you "start with the end in
mind." I'm sure you have a few sample apps already, so I'm talking
about some things like COM-ish things, XML/web services, etc.

Marshall




From: Niall Douglas <[email protected]>
To: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Fri, 2 Aug 2002 02:57:35 +0200

On 1 Aug 2002 at 3:29, Marshall Cline wrote:

> However what I'm talking about is something different. I really need
> to express the core idea more clearly. The idea of extensibility is
> usually achieved by structuring your code so 99% (ideally 100%) of
> your system is ignorant of the various derived classes. In
> particular, pretend 'ABC' is an abstract base class with pure virtual
> methods but no data and no code, and pretend 'Der1', 'Der2', ...,
> 'Der20' are concrete derived classes that inherit directly from 'ABC'.
> The goal is for 99% of the system to NEVER use the token Der1, Der2,
> ..., Der20 --- to pass all these objects as 'f(ABC* p)' or 'g(ABC&
> x)'.

That's actually what I meant previously about being able to replace a 
class in a hierarchy with a completely different one and have little 
if no knock-on effects.

> There are other problems with deep hierarchies, most especially the
> reality that they often result in "improper inheritance," and that
> causes unexpected violations of the base class's contract which
> ultimately breaks the code of "the vast majority" of the system. This
> proper-inheritance notion is the same as require-no-more,
> promise-no-less, which you basically didn't like :-(

No, I didn't like the /phrase/, not its meaning. I understood the 
meaning three emails ago.

> Big picture: Let's start over and ask, In addition to meeting the
> requirements, what are we trying to achieve? Instead of saying
> something vague like "reduce maintenance cost," I'll try to codify
> that in terms of software stability: we want the bulk of the changes
> or extensions to *not* require changes to the bulk of the system. A
> cutesy way to say this is to eliminate the ripple effect. The point
> is to try to build stuff in such a way that the bulk (say 99%) of the
> system is stable when changes or extensions are made. The above is a
> partial solution to this problem.

Agreed.

> >One of the things I look for when designing my class inheritances is
> >whether I could say, chop out one of the base classes and plug in a
> >similar but different one.
> 
> Interesting. That might be an artifact of the "other" approach to
> inheritance, since that's almost exactly the opposite of what happens
> with my code. Generally I design things so the base class is forever.
> There is no benefit to unplugging it, and in fact it is extremely
> unusual for it to be unpluggable since it codifies both the signatures
> *and* the contracts that the various derived classes must abide by,
> and plugging in another base class would almost always change those in
> some way. But again, I emphasize that I never set up unpluggable base
> classes *because* of the overall structure of my code, in particular
> the structure where 99% of the system uses base-class
> pointers/references, and that 99% is ignorant of any derived classes.

No no, I meant in terms of keeping coupling low, not at all that I 
actually intended to be swapping base classes around (that would 
indicate I hadn't designed the thing right).

> These last two paragraphs aren't describing a new model, but are
> trying to give a couple of insights about the model I've been
> describing all along.

You don't seem to believe me when I say I've integrated your wisdom! 
Trust me, I absolutely 100% understand what you have taught me - I 
learn quickly!

> >>A derived class's methods are allowed to weaken requirements
> >>(preconditions) and/or strengthen promises (postconditions), but
> >>never the other way around. In other words, you are free to
> >>override a method from a base class provided your override requires
> >>no more and promises no less than is required/promised by the method
> >>in the base class. If an override logically strengthens a
> >>requirement/precondition, or if it logically weakens a promise, it
> >>is "improper inheritance" and it will cause problems. In
> >>particular, it will break user code, meaning it will break some
> >>portion of our million-line app. Yuck.
> >>
> >>The problem with Set inheriting from Bag is Set weakens the
> >>postcondition/promise of insert(Item). Bag::insert() promises that
> >>size() *will* increase (i.e., the Item *will* get inserted), but
> >>Set::insert() promises something weaker: size() *might* increase,
> >>depending on whether contains(Item) returns true or false. 
> >>Remember: it's perfectly normal and acceptable to weaken a
> >>precondition/requirement, but it is dastardly evil to strengthen a
> >>postcondition/promise.
> >
> >This is quite ephemeral and subtle stuff. Correct application appears
> > to require considering a lot of variables.
> 
> I probably didn't describe it well since it's actually quite simple. 
> In fact, one of my concerns with most software is that it's not soft,
> and in particular it has a large ripple effect from most any change. 
> This means a programmer has to understand how all the pieces fit
> together in order to make most any change. In other words, the whole
> is bigger than the sum of the parts.

Well this is typical of any increasingly complex system - more and 
more, it is less the mere sum of its parts.

> The idea I described above means that the whole is *merely* the sum of
> the parts. In other words, an average (read stupid) programmer can
> look at a single derived class and its base class, and can, with total
> and complete ignorance of how the rest of the system is structured,
> decide unequivocally whether the derived class will break any existing
> code that uses the base class. To use another metaphor, he can be
> embedded in a huge forest, but he can know whether something new will
> break any old thing by examining just one other leaf.

That's an admirable desire, but do you think it's really possible? If 
I've learned anything from quantum mechanics and biology, it's that 
there will *always* be knock-on effects from even the tiniest change 
in any large system. Good design and coding is about minimising 
those, but as you've mentioned before all you need is one bad 
programmer to muck it all up.

> I rather like that idea since I have found the average programmer is,
> well, average, and is typically unable or unwilling to understand the
> whole. That means they screw up systems where the whole is bigger
> than the sum of the parts -- they screw up systems where they must
> understand a whole bunch of code in order to fully know whether a
> change will break anything else.

Hence the usefulness of pairing programmers.

> I call this the "middle of the bell curve" problem. The idea is that
> every company has a bell curve, and in the middle you've got this big
> pile of average people, and unfortunately these average people can't
> handle systems where the whole is bigger than the sum of the parts.
> That means we end up relying on the hot-shots whenever we need to
> change anything, and all the average guys sit around sucking their
> thumbs. I think that's bad. It's bad for the hot-shots, since they
> can never do anything interesting - they spend all their time fighting
> fires; it's bad for the average people since they're under utilized;
> and it's bad for the business since they're constantly in terror mode.

OTOH, as many BOFH's know, enhancing a company's dependence on you 
increases your power. Right at the start those expert's under whose 
wing I was which I mentioned, they would do things like turn up late 
when they felt like it and declare their own vacation time with about 
eight hours notice. I must admit, I've used my own hot-shot status 
occasionally as well - while I don't like the consequences of it 
professionally, it's an easy vicious circle to fall into.

> If we don't solve the middle-of-the-bell-curve problem, why have we
> bothered with all these fancy tools, paradigms, etc.? In other words,
> the hot-shots could *always* walk on water and do amazing things with
> code, and they don't *need* OO or block-structured or GUI builders or
> any other fancy thing. 

No, that's not true. If collected some hot-shots together and wrote 
say X ground-up in assembler - yes, absolutely, it could be done and 
done well but in productivity terms it would be a disaster.

Or, put more simply, all this OO and GUI builders and such just as 
much enhance productivity of hot-shots than the average guy. In fact, 
I'd say the more abstract we make it, the *greater* the difference in 
productivity between best and worse because the harder it is for the 
average guy to know why what he wants to do doesn't work.

> So if we don't solve the
> middle-of-the-bell-curve problem, we might as well not have bothered
> with all this OO stuff (or you could substitute "data centric stuff"),
> since we really haven't changed anything: with or without our fancy
> languages/techniques, the hot-shots can handle the complexity and the
> average guys suck their thumbs.

No, I'd have to disagree with you here. In many many ways the modern 
task of software engineering is harder than it was in the 1980's. At 
least then, you wrote your code and it worked. Nowadays, it's a much 
more subtle task because your code depends directly on millions of 
lines of other people's code, much of which wasn't written with a 
single purpose in mind. I know I've wasted days on stupid problems 
with undocumented malfunctioning - and I can only imagine for the 
less technically able (does the phrase "horrible nasty workaround" 
come to mind?)

> Finding a way to utilize the middle of the bell curve is, in my mind,
> a bulls eye for the industry as a whole. And I think the above
> approach is a partial solution.

I think there's some chance so long as the average programmer stays 
in one environment eg; just Java. As soon as they want to say tie 
Java in with COM, then all hell can break loose. And the more 
disperate technologies you bring together to try and get them to work 
as a cohesive whole, the harder it gets.

I'll put it this way: there is a definite problem in modern software 
engineering with documentation. That game I wrote for DirectX had me 
pounding my head for days because of some of the worst docs I have 
seen in recent times. Unix in general is even worse - you get your 
man or info pages which vary widely in quality. AFAICS they're not 
making the guys who write the code write the documentation, and 
that's bad.

I'll just mention RISC-OS had fantastic documentation (even with 
custom designed manuals which automatically perched on your lap). 
It's a difference I still miss today, and it's why my project has 
excellent documentation (I wrote a lot of it before the code).

> >No, I've got what you mean and I understand why. However, the point
> >is not different to what I understood a few days ago although I must
> >admit, Better = Horizontal > Vertical is a much easier rule of thumb
> >than all these past few days of discussion. You can use that rule in
> >your next book if you want :)
> 
> I've seen hierarchies with up to five levels, although all but the
> very last were abstract base classes with almost no data or code. So
> again, to me the real culprit is improper inheritance and/or
> inheritance from a data structure. The former breaks user code and
> the later can cause performance problems (creates a ripple effect when
> we try to improve performance by changing to a different data
> structure).

What would your thoughts be then on Qt, which does make some use of 
data, more data, some more data; in its class hierarchies? 

> For example, they could cause the list's contents to become
> unsorted, and that could screw up TSortedList's binary search
> algorithms. The only way to insulate yourself from this is to use
> has-a or private/protected inheritance.

Surely private or protected inheritance affects the subclass only? 
ie; you could still pass the subclass to its base class?

> >Embedded systems programming as we knew it is dying out. When 
> >desktops went all powerful, a lot of us assembler guys went into tiny
> > systems but now they've gone all powerful, it's a rapidly shrinking
> >market. The writing is definitely on the wall - move onto OO and C++
> >and such or else become unemployable.
> 
> You may have already worked with hand-held systems, but if not, they
> might be the last bastion of tight, high-tech coding. Particularly
> hand-held systems targeted at the consumer market, since that usually
> means the company wants to squeeze the unit cost and extend the
> battery life. In those cases, they worry about everything. Wasting
> memory means the thing needs more RAM or flash, and that increases
> unit cost and reduces battery life. Similarly wasting CPU cycles
> burns the battery up pretty fast. So in the end they want it very
> small and very fast, and that makes it challenging/fun.

No, that's not hugely true anymore. I worked alongside the Windows CE 
port to the ARM as well as Psion's Symbian OS and the predominant 
view was to write it much as for a desktop. After all, handhelds will 
get faster and have more memory just like a desktop. What they do is 
produce a beta copy for the development prototype which is usually 
way over-spec (ie; spec in two to three years), and then work out the 
least they can put into the production models and optimise from there 
(ie; how low can they push the clock speed + hardware features for 
the software). It's definitely not ground-up anymore, and if it comes 
down to the flash image being too big to fit they just stick more ROM 
in (it's negligible in price and battery consumption).

In fact, between two thirds and three quarters of battery power goes 
on the screen. Regarding cost, most tends to go with your chosen 
screen/chipset/peripherals whereas memory is quite cheap.

Put it this way: Symbian and WinCE are entirely C++. WinCE lets you 
port your windows app through a special recompile and removal of some 
of the more esoteric APIs. The days of assembler hacking are over.

> Limbo. It's hosted within Inferno, an OS that was originally by
> Lucent, but was sold to a UK company named Vita Nuova.
> 
> Limbo was designed by Dennis Ritchie and some other really smart folks
> (BTW I had a chance to talk to Dennis on the phone as a result of this
> engagement), and everyone involved gives it glowing reviews. But like
> I said, my client is having a hard time finding people to work with it
> since there simply aren't that many Limbo programmers out there.
> 
> Somewhat interesting approach. It's hosted via a virtual machine, and
> it's compiled into a byte-code of sorts, but it's very different from
> the stack-machine approach used by Java. It's much closer to a
> register-machine, so the source code "a = b + c" compiles into one
> instruction (pretend they're all of type 'int', which uses the 'w'
> suffix for 'word'):
> 
> addw b, c, a // adds b+c, storing result into a
> 
> The corresponding Java instructions would be something like this:
> 
> iload b // pushes b
> iload c // pushes c
> iadd // pops c then b, adds, pushes the sum
> istore a // pops the sum, stores into a
> 
> There are two benefits to the Limbo byte-code scheme: it tends to be
> more compact, on average, and it's much closer to the underlying
> hardware instructions so a JIT compiler is much smaller, faster, uses
> less memory, and is easier to write. E.g., a Java JIT has to convert
> all these stack instructions to a typical machine-code add, and that
> transformation has to happen on the fly, whereas Limbo does most of
> that transformation at compile-time.

In other words, Limbo is doing a full compile to a proper assembler 
model (which just happens not to have a processor which can run it, 
but one could be easily designed). Java is really mostly interpreted 
in that the source is pretty easy to see in the byte code. I've seen 
some reverse compilers and their output is awfully similar to the 
original - whereas no reverse compiler would have a hope 
reconstituting C++ (or even C).

> It also has some very interesting data transmission techniques, based
> on CAR Hoare's CSP (communicating sequential processes) model. By
> tying their synchronization and data transmission technique to CSP,
> they instantly know all sorts of facts that academics have proven
> about their language/environment. For example, Hoare showed that CSP
> can be used to build any other synchronization primitive (such as
> semaphores, or Java-like monitors, or anything else), and a whole
> bunch of academics created all sorts of reader/writer scenarios that
> can be exploited by the language.
> 
> The Limbo implementation of CSP is via channels. You create a channel
> in your code, then one thread reads from the channel and another
> thread writes to it. Channels aren't normally tied to files, but they
> can be. Pretty slick stuff, and somewhat related to what you're doing.
> For example, you can have a channel of 'int', or a channel of 'Xyz'
> where the latter is a struct that contains all sorts of stuff, or
> anything in between.

That's interesting, that's very similar to what I do - channels are 
lighter finer granularity form of my data streams and I've also used 
a lazy data locking approach to coordinate multiple access to 
distributed data. Mine runs like a p2p system because unfortunately, 
POSIX does not define many inter-process synchronisation mechanisms.

> The language takes some getting used to, primarily because it, unlike
> C or C++, has *no* back door to let you do things that are nasty. 
> E.g., there are no unchecked pointer casts, there is nothing
> corresponding to a 'void*' type, function pointers have to specify all
> parameters exactly, there is no is-a conversion or any other way to
> get a Foo pointer to point at a Bar object, etc. Obviously the
> byte-code level (called "Dis") lets you do these things, but Limbo
> itself tries to protect idiots from being idiotic. (As you might
> guess, I had to write some Dis code for some things. That was fine,
> of course, but it was somewhat bizarre seeing that Limbo offered no
> alternative.)
>
> You might want to read their articles about CSP. See
> www.vitanuova.com. Also check out Lucent's web site.

Actually, I went and downloaded a prebuilt version for VMWare - I'm 
sitting on its desktop right now. I must admit to being slightly 
miffed that they've also "stolen" my idea for a unified namespace, 
although theirs merely includes windows. They're also come up 
remarkably with quite a few things I had thought of independently - 
like overloading mouse button presses. I'm doing it in a way though 
that won't scare people (unlike Plan 9)

Maybe I should go apply for a job at Bell Labs? Nah, that US visa 
thing getting in the way again ...

Thanks for pointing me towards Plan 9, I wouldn't have known my ideas 
are so agreed upon by (eminent) others without it!

> I can achieve everything in straight C that I can in C++, since I can
> always manually simulate a virtual-pointer / virtual-table mechanism,
> hand-mangle function names to simulate function or operator
> overloading, manually create the state transition tables used by
> exception handling, etc. However there would be a lot of grunt
> bookkeeping code, and that would eventually cloud the ultimate goal. 
> Even if I chose a simplified set of features to simulate, it would
> still add chaff to the code and that would make it harder to work
> with. One real-world example of this is X (AKA the X Windows System).
> X was written in C using OO design, and they had all sorts of macros
> to handle inheritance and all the rest, but eventually they lost a
> grip on the system because it had too much bookkeeping crap.

No I twigged this with my C fairly early on. It only makes sense to 
munge non language supported features in so far as doing so saves you 
cost later on. Going overboard makes things worse.

That is why I chose C++ for my project, not C or assembler (like my 
previous two attempts).

> I think you just weakened your argument about OO being non-intuitive.
> Below you said:
> 
> >... can you see my point that when a newbie 
> >designs OO they tend to get it wrong? Hence my point that good OO
> >isn't intuitive, and hence my point that there is something wrong
> >with OO because a better system would be intuitive ie; complete
> >newbie has a good chance of generating a good design?
> 
> The argument you're making seems reasonable: when a newbie designs OO
> he gets it wrong, therefore good OO isn't intuitive, therefore there
> is something wrong with OO.
> 
> However, once you admit that these people are no good at design full
> stop period, the two "therefore"s go away -- the fact that they're no
> good at design full stop period provides an alternative explanation
> for why they get OO designs wrong.

No not at all - the reason partly why they are no good at design is 
because they aren't using the right schemas. When someone must 
practice something non-intuitive, they must build helper schemas to 
manage it. If they have difficulty with this, the result is poor 
understanding and especially application.

My suggestion is to give them a better schema base with which 
intuition can help them more.

> >Yes, it compiles fine. And no, I'm not sure why it does when TQString
> > does especially when I've faithfully replicated the constructor
> >hierarchy above.
> 
> Are any of the ctors in either TQString or QString listed as
> "explicit"?

No. In fact I didn't know there was an explicit keyword till now.

> Note that your 'DerivedString' has a ctor that takes a (const
> BaseString&), which is not a copy ctor. Is that intentional?
> 
> It seems very strange to me that QString would have an operator= that
> takes a (const char*), but not one that takes a (const QString&). If
> it really takes both, you might want to add them both.
> 
> Basically I'm curious and frustrated that I don't understand this one.
> If you're willing, keep adding signatures from QString/TQString to
> BaseString/DerivedString until the latter breaks. I'd be thrilled if
> you can chase this one down, but I'll obviously understand if you
> can't. (I *hate* irrational errors, because I'm always afraid I've
> missed something else. Like your "template<class type>" bug, adding a
> pointer cast made the error go away, but I don't think either of us
> were comfortable until we found the real culprit.)

I did play around some more with that but couldn't replicate the 
error. Unfortunately, I added casts to the six or so errors and now I 
can't find them anymore (I really need to put this project into CVS). 
So I am afraid it's lost for the time being - sorry.

Furthermore, I found why << and >> weren't working. It seems they 
didn't like being concantated eg; ds << keyword << metadata where 
keyword was a QString and metadata a struct with public operator 
overloads designed to stream it. I understand now QString doesn't 
know what a TQDataStream is, so it was implicitly casting up and then 
my metadata struct couldn't handle the output QDataStream instead of 
TQDataStream.

> >Ok, I'm interested now. You can point me at a webpage if one exists.
> 
> No prob. To make sure we're on the same page, let's be explicit that
> all the 'foo()' functions take the same parameter list, say an 'int'
> and a 'double', so the only difference is their return types. I'll
> first rewrite the "user code" using these parameters:
> 
> void sample(int a, double b)
> {
> int i = foo(a, b);
> char c = foo(a, b);
> float f = foo(a, b);
> double d = foo(a, b);
> String s = foo(a, b);
> }
> 
> The rules of the game are simple: if we can get a totally separate
> function to get called for each line above, we win.
> 
> The solution is trivial:
> 
> class foo {
> public:
> foo(int a, double b) : a_(a), b_(b) { }
> operator int() const { ... }
> operator char() const { ... }
> operator float() const { ... }
> operator double() const { ... }
> operator String() const { ... }
> private:
> int a_;
> double b_;
> };
> 
> QED

Let me get this: you're overloading the () operator yes? In which 
case, that's quite ingenious. I'm not sure it would prove regularly 
useful though - seems too roundabout a solution except in quite 
specific instances.

Thanks nevertheless.

> >I personally would probably have had it use static typing when it
> >could, but when the compiler didn't know it would complain unless you
> > added a modifier to say it was a dynamic cast - then the check gets
> >delayed till run time. As it happens, surely that's happened anyway
> >(albeit relatively recently) with dynamic_cast<>().
> >
> >My point is, it could have been made possible to utilise the best of
> >both worlds but with a bias toward static typing.
> 
> I think your goal is admirable. However if you think a little deeper
> about how this would actually get implemented, you would see it would
> cause C++ to run much slower than the worst Smalltalk implementation,
> and to generate huge piles of code for even trivial functions. E.g.,
> consider:
> 
> void foo(QString& a, QString& b)
> {
> a = "xyz" + b;
> }
> 
> Pretend QString's has a typical 'operator+' that is a non-member
> function (possibly a 'friend' of QString). It needs to be a
> non-member function to make the above legal. Pretend the signature of
> this 'operator+' is typical:
> 
> QString operator+ (const QString& x, const QString& y);
> 
> Thus the 'foo()' function simply promotes "xyz" to QString (via a
> QString ctor), calls the operator+ function, uses QString's assignment
> operator to copy the result, then destructs the temporary QString.

That's how it works currently, yes.

> However if your relaxed rules above let someone pass things that are
> not a QString (or one of its derived classes) for 'a' and/or 'b',
> things are much worse. (And, unfortunately, if your relaxed rules do
> not allow this, then I don't think you're getting much if any
> advantage to your relaxed rules.)
> 
> In particular, if 'a' and/or 'b' might not be QString objects, the
> compiler would need to generate code that checked, at run-time, if
> there exists any 'operator+' that can take a 'char*' and whatever is
> the type of 'a' (which it won't know until run-time). Not finding
> one, it would search for valid pointer conversions on the left, e.g.,
> 'const char*', 'void*', 'const void*'. Not finding any of those, it
> would also search for any 'operator+' that takes the type of 'b' on
> the right. Finally, if we assume 'b' actually is a QString, it would
> find a match since it could promote the type of 'b' from 'QString&' to
> 'const QString&' (that's called a cv-conversion).

Firstly, I was thinking that the compiler would produce an error 
without a special keyword which limits the overall possibilities of 
casting ie; a strong hint to limit the total number of varieties. 
Hence then much of the above searching is unnecessary.

> However it's not done yet. To make the only candidate 'operator+'
> work, it has to try to convert the left-hand parameter from 'char*' to
> whatever is on the left-side of the 'operator+' (which it would
> discover at run-time to be 'const QString&'). Eventually it will
> discover this can be done in three distinct steps: promote the 'char*'
> to 'const char*', call the QString ctor that takes a 'const char*',
> then bind a 'const QString&' to the temporary QString object. Now if
> finally has enough information to call 'operator+'.
> 
> But it's still not done, since it then has to perform even more steps
> searching for an appropriate assignment operator. (Etc., etc.)
> 
> BTW, I've greatly simplified the actual process for function and
> operator overloading. In reality, the compiler (and, under your
> scheme, the run-time system) is required to find *all* candidate
> operators that can possibly match the left-hand-side, and all that can
> possibly match the right-hand-side, then union them and get exactly
> one final match (there's some paring down as well; I don't remember
> right now). The point is that it's nasty hard, and will require a
> nasty amount of code.

I think actually your point is that doing this requires duplication 
of effort - compile-time and run-time and the two don't quite mesh 
together perfectly.

Ok, fair enough. Still, out of the OO languages I know, they seem to 
strongly tend towards either static or dynamic with no attempts to 
run a middle route. I probably am saying this out of ignorance 
though.

> C++ is not D = we can't add rules that cause legal C programs to
> generate compile errors unless there is a compelling reason to do so.

I'm not seeing that this would.

> What would happen with this:
> 
> void foo(char* dest, const char* src)
> {
> strcpy(dest, src);
> }
> 
> Or even the simple hello-world from K&R:
> 
> int main()
> {
> printf("Hello world!\n");
> return 0;
> }
> 
> Would those generate an error message ("No version of
> 'strcpy()'/'printf()' returns 'void'")?

Only if there is another overload. If there's one and one only 
strcpy(), it gets called irrespective of return just like C. If 
there's more than one, it uses the void return otherwise it generates 
an error (without a cast).

> * If they would cause an error, we break too much C.
> * If they don't cause an error, we jump from the frying pan into the
> fire: if someone later on created a version of those functions that
> overloaded by return type, all those calls would break because
> suddenly they'd all start generating error messages ("missing
> return-type cast" or something like that). In other words, the
> programmer would have to go back through and cast the return type,
> e.g., (int)printf(...) or (char*)strcpy(...).

No I think my solution preserves existing code.

> Adding a return-type-overloaded function wouldn't *always* cause an
> error message, since sometimes it would be worse - it would silently
> change the meaning of the above code. E.g., if someone created a
> 'void' version of printf() or strcpy(), the above code would silently
> change meaning from (int)printf(const char*,...) to a totally
> different function: (void)printf(const char*,...).

In this particular case, yes. I would have the message "demons abound 
here" stamped in red ink on that. My point is that C and C++ put lots 
of power into the hands of the programmer anyway, so I don't think 
the fact you can break lots of code by introducing a void return 
variant of an existing function is all that bad. There are worse 
potentials for error in the language.

> >I read Bjarne's original C++ book and found it nearly impenetrable. 
> 
> His writing is hard to read by anyone.

Ah thank god, I was thinking I was stupid!

> >Of course, that was then and this is now, but he didn't seem to me to
> > write in an overly clear style. Quite laden with technogrammar.
> 
> D&E (as the book is affectionally called) is a valuable resource for
> someone like you, since it explains why things are the way they are.
> It's probably not as hard to read as The C++ Programming Language
> since it's really a narrative or story of how Bjarne made his
> decisions and why. But even if it is hard to read, you still might
> like it. (Obviously if you have only one book to buy, buy mine, not
> his! :-) (Actually I get only a buck per book so I really have almost
> no incentive to hawk the thing.)

I'm guessing you get a 10% commission then, halved between the two of 
you. Yeah, it's not a lot ...

No, my reading time is fully occupied with philosophy, psychology and 
other humanities. I force myself to read for a half hour a day, but 
even still other things quickly eat up my time (eg; project, visiting 
people etc.).

> >I'm afraid I don't. In your 20 derived classes, each is in fact its
> >own autonomous data processor whose only commonality is that they
> >share an API. The API is good for the programmer, but doesn't help
> >the data processing one jot.
> 
> I have no idea what I was thinking above - the logic seems to totally
> escape me. Perhaps I was referring to your last sentence only, that
> is, to base your design totally around data. Yea, that's what I was
> thinking. Okay, I think I can explain it.
> 
> In my base class 'Foo', the design of the system was based around
> 'Foo' itself and the API specified by Foo. 99% of the system used
> 'Foo&' or 'Foo*', and only a small percent of the code actually knew
> anything about the data, since the data was held in the derived
> classes and 99% of the system was ignorant of those. In fact, there
> are 20 *different* data structures, one each in the 20 derived
> classes, and "99% of the system" is ignorant of all 20.
> 
> The point is the vast majority of the code (say 99%) doesn't have the
> slightest clue about the data. To me, that means the code was
> organized *not* around the data. The benefit of this is pluggability,
> extensibility, and flexibility, since one can add or change a derived
> class without breaking any of the 99%.
> 
> I'm still not sure that addresses what you were saying, but at least I
> understand what I was trying to say last night.

No that addresses programmability and maintainability. It does not 
address program efficiency, which was my point.

> >Hence my view that OO is good for organising source (intuitively it
> >produces good source organisation) but poor for program design (ok,
> >program algorithms in your terms).
> 
> I think OO has one bullet in its gun: it is good for achieving
> non-functional goals, like extensibility, flexibility, etc. If you
> are *very* careful, you can achieve those non-functional goals without
> sacrificing other non-functionals, such as speed. I think if someone
> has a program with no extensibility and no flexibility goals, then OO
> adds little genuine value. 

Err, does this mean you are agreeing with me? :)

> >My fundamental point is that I think that you have integrated many
> >beneficial and good programming practices into your internal
> >conceptualisation of what OO is and means, and you are having
> >difficulty separating them and treating them as what they are. I
> >personally prefer to treat these things more seperately as I believe
> >it offers me a great selection of tools from the toolbox as it were,
> >but it's entirely a personal choice.
> 
> You're probably right. I'm not an advocate for any given style of
> programming, since any advocate for anything ends up being a
> one-trick-pony, and they can only be radically successful if their
> particular "trick" happens to be a really good fit for the project du
> jour. Instead I try to advocate success over all, and that means
> intentionally using whatever styles help achieve that success.

Ah, agreement also. Good.

> >What then do you feel is problematic with a data-centric approach? 
> 
> That's easy: one size does not fit all. There's nothing "problematic"
> about it, but it is a style, and therefore it will be a good fit for
> some problems and a not-so-good fit for others.
> 
> >Why isn't it a better one-size-fits-all approach? 
> 
> Because there is no one-size-fits-all approach! :-)

Ok, how about a better starting approach?

> >Surely you would 
> >agree that if you base your design on quantities of data and the
> >overheads of the media in which they reside, you naturally and
> >intuitively produce a much more efficient design?
> 
> Even if what you're saying is true, "a much more efficient design"
> might not the top priority on "this" project. All I'm saying is: I
> prefer to start with the goals, *then* decide which technologies to
> use. Anyone who comes in talking, who already knows which
> technologies should be used before understanding the goals, is foolish
> in my book.
> 
> I wouldn't want to assume your data-oriented approach is the answer
> any more than I would want to assume OO is the answer. First tell me
> what the question is, *THEN* I'll come up with the "most appropriate"
> answer.

Ok, I think we're escaping the fundamental core of this thread. 
Basically, what I am saying, is that across all the software projects 
in all the world, people are mostly applying an OO-based solution as 
a primary leader. I feel this produces worse quality software because 
of the problems with lack of intuition and furthermore, if a data-
centric approach were taken instead as a primary leader, better 
quality software would emerge precisely because there is better 
chance of intuition leading you correctly. In this, I am not negating 
the use of OO at all, just saying it should not be the primary 
tackling methodology - and of course, any combination of various 
techniques should be used depending on what's the best solution.

> [My Data Centric ideas]
> >>>So, thoughts? I'm particularly interested in what you see as design
> >>>flaws 
> >>
> >>Please compare and contrast with web-services. Obviously you're not
> >>married to XML like most web-services are, but they also have a
> >>concept of components / services through which data flows. Is there
> >>some similarity? Even at the conceptual level?
>
> You might want to think about this as you go forward. XML's
> limitations are the fact that it's text based (speed, perhaps some
> limitations in its flexibility), and, paradoxically, it is too good at
> being self-describing and therefore there are some security concerns. 
> (If you're aware of that second point, skip this: if an XML glob has a
> tag somewhere that says <CreditCard>....</CreditCard>, then hackers
> know just where to look to get the info they want. If it wasn't so
> self-describing, it would be harder to hack. People are very aware of
> this problem and are working on it. Fortunately (or unfortunately??)
> the solution is trivial: encrypt all XML blobs that pass across a
> network.)
> 
> The point is that XML has some limitations, but it seems like people
> are going to be able to get 90% of what they want via XML, and then
> 95%, then 97%, etc. That progression makes your stuff less
> compelling.

True, but it depends greatly where they are taking XML. AFAICS, 
effectively all they are doing is making propriatery file formats 
public and furthermore, they're guaranteeing a certain minimum 
structure.

You still require a DTD. You still require something which can work 
with that DTD. Of course, the DOM API takes much of your generalised 
work away, but you're still lumbered with either licensing an 
interpreter for the data or building your own one in order to make 
use of the data.

Contrast with my solution: mine leverages existing facilities in that 
if I want to pull data out of say an Excel spreadsheet, my data 
converter just invokes COM and has it done. Furthermore, because of 
colaborative processing, if my Sparc box wants to root around inside 
an Excel file it merely uses a data converter running on a windows 
box. End result: identical.

Are we beginning to see business advantages yet? Hence why I said the 
networking code is the key to revenue.

> >Have you ever seen or used something that just impressed you with its
> > quality? Have you ever really enjoyed programming for a certain
> >language or operating system because it was so well designed?
> >
> >In other words, I'm targeting the 20% of programmers or so who 
> >actually like programming and do it outside of work for fun. ie; a
> >good majority of slashdot readers.
> >
> >The runtime is obviously free and the SDK will mostly be free too ie;
> > investors can't expect a return for the first two years. This is
> >because in fact we're building a software base, without which no new
> >paradigm stands a chance in hell.
> >
> >We start making money when we put in the networking code sometime
> >into the third year. 
> 
> That's a *very* hard sell to a VC guy. They have lots of people
> claiming to deliver a 100% or 1000% return in the first year. To
> admit you're a money pit that won't even generate any revenue for 3
> years (and won't probably generate profit for many more years) will be
> a *very* hard sell.

Yes but the people making those claims have been proven to be liars 
by the dot com death. They were all along, because if Amazon can't 
make a profit in how many years then sure as hell no claims of 100% 
return in the first year have any foundation in truth whatsoever.

Regarding being a money pit, if I took me and another programmer of 
my choosing (a 10x programmer like from DEC's Mythical Man Month) on 
a 20k salary with a strong percentage in company shares, we should 
only need 150,000 for the first two years.

Other way round: we can take donations during the first two years, 
say 20 euro each one. Having watched BeOS and internet radio 
stations, I genuinely think that could cover 20% of our costs - but 
only into the second year onwards.

> Think about using Cygnus's business model. Cygnus was Michael
> Tiemann's old company (I think they were acquired by Red Hat). They
> had a similar goal as yours, only they had much more popular tools,
> e.g., the GNU C and C++ compilers, etc., etc. Michael wrote the first
> version of g++, then rms (Stallman) got back into the mix and they
> worked together on a brand new GCC that combined C, C++, Objective C,
> Java, FORTRAN, and probably Swahili into the same compiler. The point
> is that the GNU tools are free, but Cygnus got revenue by charging
> corporations for support. Big companies in the US are afraid of free
> software. They want *somebody* they can call and say, "I need you to
> fix this bug NOW!" So Michael offered them a one-day turn-around on
> bugs (or something like that) for $20,000/year (or something like
> that). He told them he'd distribute the bug-fix to everyone free of
> charge, but they didn't care: they wanted to know THEIR programmers
> wouldn't get hung up on bugs, so it made business sense.

Already well ahead of you on that one. Worst comes to worst and I 
fail to attract a penny - well, then it's time to GPL the lot and 
pray I can make it into something I can consult on in the future.

> >However, I honestly don't know. I look at COM and I think my solution
> > is likely to require less overhead. I won't know until I have
> >working code to benchmark. I will say though I have gone to great
> >lengths to optimise the system.
> 
> Think about that - it might be worthwhile to create a few "sample
> apps" and a few "sample app programmers," that way you "start with the
> end in mind." I'm sure you have a few sample apps already, so I'm
> talking about some things like COM-ish things, XML/web services, etc.

Yeah I threw together a JPEG=>Image converter to test the 
"funability" of writing for my project. I'm a big advocate of test 
suites, so a few of those will be written.

A question your expertise may be able to answer: is there a non-GPL 
portable Unix shell because I've looked *everywhere* and can't find 
one? I would like to modify it to use my project's namespace as the 
filing system instead. Failing this, I'll take a copy of Flex and get 
it to spit out some parser C++ from which I'll make a simple shell.

The reason I need one is that a shell will be invoked a lot. I've 
borrowed off RISC-OS the concept that data type associations, icons 
etc. should be built dynamically instead of using any kind of 
registry or central database. The shell will coordinate these 
actions.

Failing that, how about a free non-GPL functional language which 
isn't a pain to use? I've experimented with Hugs (a Haskell 
interpreter) but Haskell is too esoteric for me.

Cheers,
Niall




From: "Marshall Cline" <[email protected]>
To: "'Niall Douglas'" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Sat, 3 Aug 2002 03:28:21 -0500

Niall Douglas wrote:
>On 1 Aug 2002 at 3:29, Marshall Cline wrote:
>
>>However what I'm talking about is something different. I really need
>>to express the core idea more clearly. The idea of extensibility is
>>usually achieved by structuring your code so 99% (ideally 100%) of
>>your system is ignorant of the various derived classes. In
>>particular, pretend 'ABC' is an abstract base class with pure virtual
>>methods but no data and no code, and pretend 'Der1', 'Der2', ...,
>>'Der20' are concrete derived classes that inherit directly from 'ABC'.
>> The goal is for 99% of the system to NEVER use the token Der1, Der2,
>>..., Der20 --- to pass all these objects as 'f(ABC* p)' or 'g(ABC&
>>x)'.
>
>That's actually what I meant previously about being able to replace a 
>class in a hierarchy with a completely different one and have little 
>if no knock-on effects.
>
>>There are other problems with deep hierarchies, most especially the
>>reality that they often result in "improper inheritance," and that
>>causes unexpected violations of the base class's contract which
>>ultimately breaks the code of "the vast majority" of the system. This
>>proper-inheritance notion is the same as require-no-more,
>>promise-no-less, which you basically didn't like :-(
>
>No, I didn't like the /phrase/, not its meaning. I understood the 
>meaning three emails ago.

Regarding "three emails ago," we seem to have had a small communication
problem. I re-explained things after you already "got it," and I
apologize for frustrating you that way. I certainly did not want to
imply you are stupid or thick-headed or something, since it is quite
clear (to me, anyway) that you are not.

However I think we both played a part in this communication problem.
For example, when I first explained the "require no more and promise no
less" idea in my previous email, you replied, "This is quite ephemeral
and subtle..." Although it is clearly subtle at times, I see it as the
opposite of ephemeral, and since, perhaps as a back-handed compliment to
you (e.g., "I *know* this guy is bright, so if he thinks it is
ephemeral, I must not have explained it very well"), I re-explained it.

There have been several other times throughout our conversation when you
said something that made me think, "He still doesn't see what I'm
seeing." I see now I was wrong, so I'm not trying to convince you that
you don't get it. I'm simply trying to help you see why I re-explained
things too many times.

For example, when you explained that you had already checked to make
sure 'prepend()' and 'append()' were not called within the base class's
code, and that that gave you confidence there wouldn't be any errors
resulting from your redefining those methods in TSortedList, I thought
to myself, "He doesn't get it yet; checking the base class itself is
necessary but not sufficient." So I (erroneously) explained it again.

Similarly when you said, "One of the things I look for when designing my
class inheritances is whether I could say, chop out one of the base
classes and plug in a similar but different one." This is a very
strange concept to me, and I don't think it makes sense when inheritance
is used as I have described, so I (erroneously) thought you didn't get
it and explained it again. :-)

Clearly *some* of the things you said made it seem like you got it, but
hopefully you can see how I (perhaps misinterpreted) others in a way
that made you seem like you didn't get it. As I said before, I'm not
accusing you of not getting it. I'm simply explaining why I erroneously
thought you didn't get it, and why I therefore explained it again (and
again ;-).

Put it this way: stupid people say random things. They are incapable of
coming up with a cohesive perspective on complex things, so their
statements are often inconsistent with each other. You said some things
that (I thought!) were inconsistent with each other, but you're not
stupid (or if you are, you sure fooled me ;-) If I had thought you were
stupid, I probably would have politely ended the conversation and
quietly written you off as a lost cause (sorry if that is condescending,
but we both have better things to do with our lives than pour hours and
hours into people we can't actually help). So instead of writing you
off, I figured, "Just one more email and he'll *really* get it!"

Hopefully that explains why (I believe) we both played a role in me
being a broken-record. And also, hopefully it shows you that I didn't
repeat myself because I thought you were dumb, or because I was dumb,
but instead because I thought you were bright enough to get it, and if
you saw it from just one more vantage point, you'd get it.

Okay, so you get it. Now we can move on!


>>These last two paragraphs aren't describing a new model, but are
>>trying to give a couple of insights about the model I've been
>>describing all along.
>
>You don't seem to believe me when I say I've integrated your wisdom! 
>Trust me, I absolutely 100% understand what you have taught me - I 
>learn quickly!

Okay, I believe you now. Hit me with a big enough hammer and I stop
repeating myself. I think.


>>>>A derived class's methods are allowed to weaken requirements
>>>>(preconditions) and/or strengthen promises (postconditions), but
>>>>never the other way around. In other words, you are free to
>>>>override a method from a base class provided your override requires
>>>>no more and promises no less than is required/promised by the method
>>>>in the base class. If an override logically strengthens a
>>>>requirement/precondition, or if it logically weakens a promise, it
>>>>is "improper inheritance" and it will cause problems. In
>>>>particular, it will break user code, meaning it will break some
>>>>portion of our million-line app. Yuck.
>>>>
>>>>The problem with Set inheriting from Bag is Set weakens the
>>>>postcondition/promise of insert(Item). Bag::insert() promises that
>>>>size() *will* increase (i.e., the Item *will* get inserted), but
>>>>Set::insert() promises something weaker: size() *might* increase,
>>>>depending on whether contains(Item) returns true or false. 
>>>>Remember: it's perfectly normal and acceptable to weaken a
>>>>precondition/requirement, but it is dastardly evil to strengthen a
>>>>postcondition/promise.
>>>
>>>This is quite ephemeral and subtle stuff. Correct application appears
>>> to require considering a lot of variables.
>>
>>I probably didn't describe it well since it's actually quite simple. 
>>In fact, one of my concerns with most software is that it's not soft,
>>and in particular it has a large ripple effect from most any change. 
>>This means a programmer has to understand how all the pieces fit
>>together in order to make most any change. In other words, the whole
>>is bigger than the sum of the parts.
>
>Well this is typical of any increasingly complex system - more and 
>more, it is less the mere sum of its parts.
>
>>The idea I described above means that the whole is *merely* the sum of
>>the parts. In other words, an average (read stupid) programmer can
>>look at a single derived class and its base class, and can, with total
>>and complete ignorance of how the rest of the system is structured,
>>decide unequivocally whether the derived class will break any existing
>>code that uses the base class. To use another metaphor, he can be
>>embedded in a huge forest, but he can know whether something new will
>>break any old thing by examining just one other leaf.
>
>That's an admirable desire, but do you think it's really possible? 

Yes, in the sense that I've seen big projects get 80% or 90% of the way
there. The key, of course, is to find low-budget ways to get the first
80% of the value, and given the 80/20 rule, that turns out to be
possible. I call it the low-hanging fruit. It's not perfect, but it's
much better than if we go with the status quo.

The low-hanging fruit involves just a few disciplines, including
"programming by contract" (you may be familiar with this; if not, sneak
a peek at chapters 1-4 of Bertrand Meyers's book, Object-Oriented
Software Construction; or I could explain it to you) and it requires
design reviews where the contracts in base classes are carefully
evaluated based on some pretty straightforward criteria. Since many
programmers don't have self-discipline, even when it would be in their
own best interest, project leaders must enforce the above by putting
them into the project's process. In the end, these things actually will
happen if they get tracked and reviewed (read "enforced") by management,
and they really do contribute a great deal toward the above goal.

There are a few other design and programming ideas I use to help achieve
that goal (more low-hanging fruit). For example, simple (but often
overlooked) things like creating an explicit architecture with explicit
APIs in each subsystem, wrapping the API of each OO subsystem so the
other subsystems can't "see" the OO subsystem's inheritances or how it
allocates methods to objects (in my lingo, so they can't see it's
design), etc.


>If 
>I've learned anything from quantum mechanics and biology, it's that 
>there will *always* be knock-on effects from even the tiniest change 
>in any large system. Good design and coding is about minimising 
>those, 

Agreed: bad design has a huge ripple effect, which can be thought of as
chaos (a tiny change in one places causes a very large change somewhere
else).


>but as you've mentioned before all you need is one bad 
>programmer to muck it all up.

All I can say for sure is that that doesn't seem to be a problem in
practice. Perhaps the added "process" smooths out the effects of the
bad programmers, I don't know. May be that's another argument in favor
of pairing programmers (though I can only make that argument
theoretically: since I haven't done a project with pair programming, I
can't say that the above disciplines are necessary and/or sufficient to
achieve the goal of the-whole-is-merely-the-sum-of-the-parts).


>>I rather like that idea since I have found the average programmer is,
>>well, average, and is typically unable or unwilling to understand the
>>whole. That means they screw up systems where the whole is bigger
>>than the sum of the parts -- they screw up systems where they must
>>understand a whole bunch of code in order to fully know whether a
>>change will break anything else.
>
>Hence the usefulness of pairing programmers.

Perhaps you're right. I always worry about know-it-all programmers who
insist on their own way even when they're wrong, but like I said, that's
more of a theoretical concern about pair programming since I haven't
experienced it in practice.


>>I call this the "middle of the bell curve" problem. The idea is that
>>every company has a bell curve, and in the middle you've got this big
>>pile of average people, and unfortunately these average people can't
>>handle systems where the whole is bigger than the sum of the parts.
>>That means we end up relying on the hot-shots whenever we need to
>>change anything, and all the average guys sit around sucking their
>>thumbs. I think that's bad. It's bad for the hot-shots, since they
>>can never do anything interesting - they spend all their time fighting
>>fires; it's bad for the average people since they're under utilized;
>>and it's bad for the business since they're constantly in terror mode.
>
>OTOH, as many BOFH's know, enhancing a company's dependence on you 
>increases your power. Right at the start those expert's under whose 
>wing I was which I mentioned, they would do things like turn up late 
>when they felt like it and declare their own vacation time with about 
>eight hours notice. I must admit, I've used my own hot-shot status 
>occasionally as well - while I don't like the consequences of it 
>professionally, it's an easy vicious circle to fall into.

Yes, but I've often found the hot-shots willing to go along with this
approach. I've never tried to fix the 8-hour-notice-for-vacation
problem, and probably couldn't if I tried, but I have had success with
the particular situation of helping empower the
middle-of-the-bell-curve.

Perhaps that success has been because I usually present some of these
ideas in front of a group/seminar, and I usually (verbally) beat my
chest and say something like, "Anybody who hurts their company to
benefit themselves is unprofessional and deserves to be fired."
Basically I shame the hot-shots into realizing they can't hold the
company hostage just for their own personal job security. In fact, I'll
be giving that sort of speech at a development lab of UPS on Tuesday,
and I'll probably mention the recent corporate scandals. If I do, I'll
talk about those disreputable people who hurt the stockholders in order
to line their own pockets. Nobody wants to be compared to those guys,
which brings out a righteous streak in everybody (including the
hot-shots).

(Sometimes, when I really need to bring out a big hammer, I compare the
"wrong" attitude to terrorism. It's a stretch, but I'm pretty good in
front of a crowd. The key insight is that nobody should be allowed to
hurt their company just to help themselves. For example, I say
something like, "If you hold company assets in between your earlobes,
they don't belong to you - they belong to the company. If you refuse to
write them down just to protect your own job, you're no better than a
terrorist, since you're basically saying, "Give me my way or I'll hurt
you." Amazingly I haven't been lynched yet.)

Like I said, I doubt any of this saber-rattling actually changes
people's hearts, and therefore it won't change the guy who gives 8 hours
notice before vacation, but it does seem to force the hot-shots into
supporting the plan, and invariably they buy-in.


>>If we don't solve the middle-of-the-bell-curve problem, why have we
>>bothered with all these fancy tools, paradigms, etc.? In other words,
>>the hot-shots could *always* walk on water and do amazing things with
>>code, and they don't *need* OO or block-structured or GUI builders or
>>any other fancy thing. 
>
>No, that's not true. If collected some hot-shots together and wrote 
>say X ground-up in assembler - yes, absolutely, it could be done and 
>done well but in productivity terms it would be a disaster.

You're probably right about what you're saying, but that's a totally
different topic from what I was *trying* to say. I'm not talking about
productivity differences (either between those who use assembler vs.
high-level tools or between the hot-shots and the dolts). I'm talking
about the ability to understand the whole ripple effect of a change in
their heads. In other words, the *average* architect is broad but
shallow (they know a little about the whole system, but often don't know
enough to reach in and change the code), and the *average* "coder" is
deep but narrow (they have deep understanding of a few areas within the
system, but most can't tell you how all the pieces hold together - they
don't have the breadth of an architect). But knowing the full ripple
effect of a change to the system often requires both broad and deep
knowledge that very few possess. In one of my consulting gigs, there
was a guy (Mike Corrigan) who was both broad and deep. Management
called him a "system-wide expert." Everyone believed he could visualize
the entire system in his head, and it seemed to be true. It was a huge
system (around two million lines below a major interface, and around 14
million lines above that). When someone would propose a change, he
would go into never-never land, and perhaps a week later he would
explain why it couldn't be done or would provide a list of a dozen
subsystems that would be effected.

When I wrote the above, I was visualizing the Mike Corrigans of the
world. After they get enough experience with a system, they are both
deep and broad, and in particular, they can see the entire ripple effect
in their heads. That's what I was trying to say (though you can't see
the connection without seeing the paragraph prior to the paragraph that
begins, "If we don't solve the middle-of-the-bell-curve problem...")


>>So if we don't solve the
>>middle-of-the-bell-curve problem, we might as well not have bothered
>>with all this OO stuff (or you could substitute "data centric stuff"),
>>since we really haven't changed anything: with or without our fancy
>>languages/techniques, the hot-shots can handle the complexity and the
>>average guys suck their thumbs.
>
>No, I'd have to disagree with you here. In many many ways the modern 
>task of software engineering is harder than it was in the 1980's. At 
>least then, you wrote your code and it worked. Nowadays, it's a much 
>more subtle task because your code depends directly on millions of 
>lines of other people's code, much of which wasn't written with a 
>single purpose in mind. I know I've wasted days on stupid problems 
>with undocumented malfunctioning - and I can only imagine for the 
>less technically able (does the phrase "horrible nasty workaround" 
>come to mind?)

Perhaps you would agree with this: In most companies, those in the
middle-of-the-bell-curve have a difficult time reliably changing large
systems. Companies that solve this problem will be better off.


>I'll put it this way: there is a definite problem in modern software 
>engineering with documentation. That game I wrote for DirectX had me 
>pounding my head for days because of some of the worst docs I have 
>seen in recent times. Unix in general is even worse - you get your 
>man or info pages which vary widely in quality. AFAICS they're not 
>making the guys who write the code write the documentation, and 
>that's bad.

(If you're already familiar with programming-by-contract, you can ignore
this.)

This is an example of programming-by-contract, by the way, only with
programming-by-contract, we normally don't think of the software being
used as if it was shipped by a third party. I.e., in
programming-by-contract, I would write a good contract / specification
for a function I wrote, even if that function was going to be used only
by my department.

So in a sense, programming-by-contract is a small-granular version of
what you'd like to see from these third-party vendors.


>I'll just mention RISC-OS had fantastic documentation (even with 
>custom designed manuals which automatically perched on your lap). 
>It's a difference I still miss today, and it's why my project has 
>excellent documentation (I wrote a lot of it before the code).

Bingo. Good stuff. I do the same thing: write the specs as best I can
ahead of time, and invariably add to them as I get further into the
project, and sometimes (carefully) changing what I wrote when it was
wrong or inconsistent (or when I simply discover a better way).

When "selling" companies on this idea, I even use a different term than
documentation, since most people think of documentation as something you
write that *describes* what the code already does. But these
contracts/specifications *prescribe* what the code *should* do, hence
the different name: contracts or specifications.


>>>No, I've got what you mean and I understand why. However, the point
>>>is not different to what I understood a few days ago although I must
>>>admit, Better = Horizontal > Vertical is a much easier rule of thumb
>>>than all these past few days of discussion. You can use that rule in
>>>your next book if you want :)
>>
>>I've seen hierarchies with up to five levels, although all but the
>>very last were abstract base classes with almost no data or code. So
>>again, to me the real culprit is improper inheritance and/or
>>inheritance from a data structure. The former breaks user code and
>>the later can cause performance problems (creates a ripple effect when
>>we try to improve performance by changing to a different data
>>structure).
>
>What would your thoughts be then on Qt, which does make some use of 
>data, more data, some more data; in its class hierarchies? 

Don't know enough about Qt to comment.

Generally speaking, good C++ (or Java or Eiffel) class hierarchies are
short and fat with very little data in the base classes. The
fundamental reason for this is that these languages exhibit an assymetry
between data and code. In particular, they let a derived class replace
some code in the base class (virtual functions), but they don't let a
derived class do the same with data (they don't have "virtual data").
Once a base class has a certain data structure, all derived classes
forever and ever are forced to have that data structure. If a given
derived class doesn't "want" that data structure, it has to carry it
around anyhow, and that typically makes it bigger or slower than
optimal.

For example, suppose we have a hierarchy representing shapes on a 2D
Euclidean plane, with class Square inheriting from Rectangle inheriting
from Polygon inheriting from Shape. Assume Shape is abstract with no
data whatsoever, but suppose we decide to make Polygon concrete. In
this case a Polygon would have some sort of list of x-y points, say a
GList<Point>. Unfortunately that would be a very bloated representation
for a Rectangle, since all we really need for a rectangle is a single
point, a width, a height, and perhaps a rotation angle. It's even worse
for Square, since Square needs only a single point plus a width (and
possibly a rotation angle).

This illustrates another peculiarity about OO, which is not typically
discussed, as far as I know. The peculiarity is that the data hierarchy
and the is-a hierarchy go in opposite directions. Put it this way:
inheritance lets you *add* data to whatever was declared in the base
class, but in many cases you really want to subtract that data instead.
For example, take Square inheriting from Rectangle. From a data
perspective (that is, when we think of inheritance as a reuse
mechanism), it makes a lot more sense to inherit the thing backwards,
after all, Rectangle (the derived class) could simply augment its base
class's 'width_' datum with its own distinct 'height_' datum, and it
could then simply override the 'height()' method so it returns 'height_'
instead:

class Square {
public:
Square(double size) : width_(size) { }
virtual double width() const { return width_; }
virtual double height() const { return width_; }
virtual double area() const { return width() * height(); }
virtual void draw() const { /*uses width() & height()*/ }
private:
double width_;
};

// This is backwards from is-a, but it
// makes sense from data and reuse...

class Rectangle : public Square {
public:
Rectangle(double width, double height)
: Square(width), height_(height) { }
virtual double height() const { return height_; }
private:
double height_;
};


Obviously this is backwards semantically, and is therefore bad based on
everything we've discussed forever (which you already get; I know). But
it is interesting how the "inheritance is for reuse" idea pushes you in
exactly the wrong direction. It is also interesting (and somewhat sad)
that the proper use of inheritance ends up requiring you to write more
code. That would be solvable if today's OO languages had virtual data,
but I'm not even sure how that could be implemented in a typesafe
language.

I actually do know of another way to have-your-cake-and-eat-it-too in
the above situation. Too late to describe this time; ask me someday if
you're interested. (And, strangely enough, although C++ and Java and
Eiffel don't support this better approach, OO COBOL of all languages
does. So if anybody ever asks you which is the most advanced OO
language, tell them COBOL. And snicker with an evil, Boris Karlov-like
laugh.)


>>For example, they could cause the list's contents to become
>>unsorted, and that could screw up TSortedList's binary search
>>algorithms. The only way to insulate yourself from this is to use
>>has-a or private/protected inheritance.
>
>Surely private or protected inheritance affects the subclass only? 
>ie; you could still pass the subclass to its base class?

I have no idea what you're asking - sorry, I simply can't parse it.

Here's the deal with private/protected inheritance. Where public
inheritance "means" is-substitutable-for, private and protected
inheritance "mean" has-a (AKA aggregation or composition). For example,
if TSortedList privately inherited from GList, then it would be
semantically almost the same as if TSortedList had-a GList as a private
member. In both cases (private inheritance and has-a) the methods of
GList (including the now infamous prepend() and append()) would *not*
automatically be accessible to users of TSortedList, and in both cases
users of TSortedList could not pass a TSortedList to a function that is
expecting a GList (which, as you know, is the start of all our "improper
inheritance" problems).

There are a few differences between private inheritance and has-a, but
the differences are buried in the code of TSortedList. In particular,
if TSortedList privately inherited from GList, the derived class's code
(its member and friend functions) are allowed to convert a TSortedList&
to a GList& (and similarly for pointers), and TSortedList can use a
special syntax to make selected public methods of GList public within
TSortedList. For example, if there is a 'size()' method within GList
that TSortedList wants to make public, then in the public section of
TSortedList simply say "using GList<T>::size;". Note: the '()' were
omitted intentionally. See the FAQ for this 'using' syntax. In fact,
the FAQ has a whole section on private and protected inheritance - you
might want to read it since it will solve your "bad inheritance"
problem, at least with TSortedList.


>>>Embedded systems programming as we knew it is dying out. When 
>>>desktops went all powerful, a lot of us assembler guys went into tiny
>>> systems but now they've gone all powerful, it's a rapidly shrinking
>>>market. The writing is definitely on the wall - move onto OO and C++
>>>and such or else become unemployable.
>>
>>You may have already worked with hand-held systems, but if not, they
>>might be the last bastion of tight, high-tech coding. Particularly
>>hand-held systems targeted at the consumer market, since that usually
>>means the company wants to squeeze the unit cost and extend the
>>battery life. In those cases, they worry about everything. Wasting
>>memory means the thing needs more RAM or flash, and that increases
>>unit cost and reduces battery life. Similarly wasting CPU cycles
>>burns the battery up pretty fast. So in the end they want it very
>>small and very fast, and that makes it challenging/fun.
>
>No, that's not hugely true anymore.

We probably have different experience bases, since most of the companies
I've worked with have such tight coding constraints that bloatware like
WinCE are unthinkable. The folks I've worked with try to squeeze the
whole shebang, including OS, apps, data, drivers, wireless stuff,
everything, into 4MB, sometimes less. They often end up with much
smaller, proprietary OSs.


>I worked alongside the Windows CE 
>port to the ARM as well as Psion's Symbian OS and the predominant 
>view was to write it much as for a desktop. After all, handhelds will 
>get faster and have more memory just like a desktop. 

That makes sense, and once you're willing to throw enough hardware at
it, e.g., 32MB and 400MHz, you can say that sort of thing.

But in the world of, say, calculators, customers expect the thing to run
for a year on an AA battery or two. The screens on those things aren't
backlit, but they have oodles of code in them. I remember working with
an HP team that said they had half a million lines of code in one of
their calculators. It solves differential equations, plots various
kinds of graphs, is programmable, etc., etc., and the amount of memory
it has is important to them.

I have a Palm V. Doesn't do too much, has a lousy 160x160 LCD
black-and-green screen, and it came with only 4MB of memory, but it's
tiny, inexpensive, weighs only 4(?) ounces, and runs for a week between
charges. I could have purchased a WinCE box, but it's much heavier,
more expensive, and needs to be charged much more often.

Now the world is slowly moving toward higher power processors and
backlit screen, which ultimately means people will begin to expect less
and less wrt battery life. In the mean time, there will always be
companies like HP, Texas Instruments, UPS, FedEx, Brooklyn Union Gas,
etc., that are up against memory limitations, and they'll always be
fighting to add functionality without increasing hardware. I think that
stuff is fun.


>>Limbo. It's hosted within Inferno, an OS that was originally by
>>Lucent, but was sold to a UK company named Vita Nuova.
>>
>>Limbo was designed by Dennis Ritchie and some other really smart folks
>>(BTW I had a chance to talk to Dennis on the phone as a result of this
>>engagement), and everyone involved gives it glowing reviews. But like
>>I said, my client is having a hard time finding people to work with it
>>since there simply aren't that many Limbo programmers out there.
>>
>>Somewhat interesting approach. It's hosted via a virtual machine, and
>>it's compiled into a byte-code of sorts, but it's very different from
>>the stack-machine approach used by Java. It's much closer to a
>>register-machine, so the source code "a = b + c" compiles into one
>>instruction (pretend they're all of type 'int', which uses the 'w'
>>suffix for 'word'):
>>
>> addw b, c, a // adds b+c, storing result into a
>>
>>The corresponding Java instructions would be something like this:
>>
>> iload b // pushes b
>> iload c // pushes c
>> iadd // pops c then b, adds, pushes the sum
>> istore a // pops the sum, stores into a
>>
>>There are two benefits to the Limbo byte-code scheme: it tends to be
>>more compact, on average, and it's much closer to the underlying
>>hardware instructions so a JIT compiler is much smaller, faster, uses
>>less memory, and is easier to write. E.g., a Java JIT has to convert
>>all these stack instructions to a typical machine-code add, and that
>>transformation has to happen on the fly, whereas Limbo does most of
>>that transformation at compile-time.
>
>In other words, Limbo is doing a full compile to a proper assembler 
>model (which just happens not to have a processor which can run it, 
>but one could be easily designed). Java is really mostly interpreted 
>in that the source is pretty easy to see in the byte code. I've seen 
>some reverse compilers and their output is awfully similar to the 
>original - whereas no reverse compiler would have a hope 
>reconstituting C++ (or even C).

Yes Limbo does a full compile to an abstract machine. But I don't agree
with one of your implications: The fact that Java uncompilers do a
pretty good job does not mean that Java is mostly interpreted. In fact,
the real reason Java uncompilers do a pretty good job is because of all
the "meta data" Java .class files are required to carry around (they
have to contain enough meta data that "reflection" can work; e.g., Java
lets you poke around in the meta data of another class, including
determining at runtime what methods it has, the parameters/return types
of those methods, which methods are private, protected, public; etc.,
etc.). If a Limbo binary (a .dis file) contained the meta-data that a
Java .class file contained, a Limbo uncompiler could do about as good a
job as a Java uncompiler.

Agree that it would be hard to uncompile C or C++, even if there was
meta-data, since it would be much harder to guess what the original code
looked like from the instruction stream. But I think that's an artifact
of the fact that a "real" processor has very small granular instructions
and the optimizer is expected to reorganize things to make them
fast/small/whatever. In contrast, the byte-codes for Limbo and Java do
more work (e.g., both have a "call" instruction that does a *whole* lot
more than a "real" hardware call), so they expect the virtual machine
itself to be optimized for the particular hardware platform.

FYI Java has a number of processors that run it. In that case, wouldn't
the assembly language look just like Java byte-codes?


>>The language takes some getting used to, primarily because it, unlike
>>C or C++, has *no* back door to let you do things that are nasty. 
>>E.g., there are no unchecked pointer casts, there is nothing
>>corresponding to a 'void*' type, function pointers have to specify all
>>parameters exactly, there is no is-a conversion or any other way to
>>get a Foo pointer to point at a Bar object, etc. Obviously the
>>byte-code level (called "Dis") lets you do these things, but Limbo
>>itself tries to protect idiots from being idiotic. (As you might
>>guess, I had to write some Dis code for some things. That was fine,
>>of course, but it was somewhat bizarre seeing that Limbo offered no
>>alternative.)
>>
>>You might want to read their articles about CSP. See
>>www.vitanuova.com. Also check out Lucent's web site.
>
>Actually, I went and downloaded a prebuilt version for VMWare - I'm 
>sitting on its desktop right now. I must admit to being slightly 
>miffed that they've also "stolen" my idea for a unified namespace, 

What's that expression about great minds / great men stealing ideas.


>although theirs merely includes windows. They're also come up 
>remarkably with quite a few things I had thought of independently - 
>like overloading mouse button presses. I'm doing it in a way though 
>that won't scare people (unlike Plan 9)

The interesting thing to me is that Inferno is built *around* Limbo.
For example, whereas pid in Unix is a process ID, in Inferno it's a
thread-ID, and each thread runs a different .dis file. In other words,
running an application in Unix forks a process with its own address
space, but in Inferno it simply spawns a thread in the shared address
space, and runs a .dis file along with all the other .dis files that are
currently running.

I'm not doing it justice. I'll try again: Inferno is nothing but one
big Limbo interpreter. Every program in Inferno, including the shell
itself, utilities like 'ls', and all the rest, are Dis threads that are
runing in the same address space by the same Dis "virtual machine." The
shared address space thing seems horribly error prone, and perhaps it
is, but you've already seen how namespaces can be trimmed for individual
applications ("threads"), so apparently that's how they keep these
different apps separated. It seems like a slick, lightweight idea.

(BTW you mentioned Plan 9. Is that what you got? Or did you get
Inferno? I've been discussing Inferno. I believe Inferno stole the
namespace idea from Plan 9, but I don't know that Plan 9 is built around
a Dis virtual machine the way Inferno is. Also I'm not sure about Limbo
on Plan 9.)


>Maybe I should go apply for a job at Bell Labs? Nah, that US visa 
>thing getting in the way again ...

Vita Nuova is in the UK, and they own the rights to Plan 9 and Inferno.


>>(BTW if you correspond with Vita Nuova about Limbo, please don't say,
>>"Marshall Cline said..." since I don't want to cause any hurt feelings
>>by calling their baby ugly. Thanks.)
>
>Heh, I am the master of discretion in these matters! I'll even modify 
>the online posting I was going to do of this commentary so those 
>sections are removed.

Thanks.


>Thanks for pointing me towards Plan 9, I wouldn't have known my ideas 
>are so agreed upon by (eminent) others without it!

You're famous and you didn't know it.


>>Note that your 'DerivedString' has a ctor that takes a (const
>>BaseString&), which is not a copy ctor. Is that intentional?
>>
>>It seems very strange to me that QString would have an operator= that
>>takes a (const char*), but not one that takes a (const QString&). If
>>it really takes both, you might want to add them both.
>>
>>Basically I'm curious and frustrated that I don't understand this one.
>>If you're willing, keep adding signatures from QString/TQString to
>>BaseString/DerivedString until the latter breaks. I'd be thrilled if
>>you can chase this one down, but I'll obviously understand if you
>>can't. (I *hate* irrational errors, because I'm always afraid I've
>>missed something else. Like your "template<class type>" bug, adding a
>>pointer cast made the error go away, but I don't think either of us
>>were comfortable until we found the real culprit.)
>
>I did play around some more with that but couldn't replicate the 
>error. Unfortunately, I added casts to the six or so errors and now I 
>can't find them anymore (I really need to put this project into CVS). 
>So I am afraid it's lost for the time being - sorry.
>
>Furthermore, I found why << and >> weren't working. It seems they 
>didn't like being concantated eg; ds << keyword << metadata where 
>keyword was a QString and metadata a struct with public operator 
>overloads designed to stream it. I understand now QString doesn't 
>know what a TQDataStream is, so it was implicitly casting up and then 
>my metadata struct couldn't handle the output QDataStream instead of 
>TQDataStream.

Of course! The expression (out << xyz) usually returns a stream
reference that refers to 'out' itself, that way they can be cascaded,
e.g., out << xyz << pqr << abc. But if out is a base-class stream, then
the type of (out << xyz) is base-class-stream-reference, and then the
'<< pqr' part won't make sense to the compiler unless it too is defined
on the base-class-stream.

If your << functions can work with the base-class-stream on the
left-hand-side, then change your << methods to friend functions and
insert an explicit left-hand-side to be base-class-ref. E.g., change
this *member* function:

TQDataStream& operator<< (const Foo& xyz);

and its corresponding definition:

TQDataStream& TQDataStream::operator<< (const Foo& xyz)
{
...
return *this;
}

to the following friend function declaration:

friend QDataStream& operator<< (QDataStream& s, const Foo& xyz);

and its corresponding definition:

QDataStream& operator<< (QDataStream& s, const Foo& xyz)
{
...
return s;
}


>>>Ok, I'm interested now. You can point me at a webpage if one exists.
>>
>>No prob. To make sure we're on the same page, let's be explicit that
>>all the 'foo()' functions take the same parameter list, say an 'int'
>>and a 'double', so the only difference is their return types. I'll
>>first rewrite the "user code" using these parameters:
>>
>> void sample(int a, double b)
>> {
>> int i = foo(a, b);
>> char c = foo(a, b);
>> float f = foo(a, b);
>> double d = foo(a, b);
>> String s = foo(a, b);
>> }
>>
>>The rules of the game are simple: if we can get a totally separate
>>function to get called for each line above, we win.
>>
>>The solution is trivial:
>>
>> class foo {
>> public:
>> foo(int a, double b) : a_(a), b_(b) { }
>> operator int() const { ... }
>> operator char() const { ... }
>> operator float() const { ... }
>> operator double() const { ... }
>> operator String() const { ... }
>> private:
>> int a_;
>> double b_;
>> };
>>
>>QED
>
>Let me get this: you're overloading the () operator yes? 

Close. It's called the cast-operator. It's used in cases when you want
to allow your object of class Foo to be converted to an object of class
Bar. For example, the statement:

if (cin >> xyz) {
...
}

says that (cin >> xyz) must be some sort of boolean-ish thing. But we
know the type of (cin >> xyz) is 'istream&', since we know that these
can be cascaded, e.g., cin >> xyz >> pqr >> abc. So how could
'istream&' appear in a boolean context? Answer: the cast operator.
'istream' has a method called 'operator boolean() const' that returns
'true' if the stream is in a good state (meaning it didn't dected any
errors in preceding input operations), or 'false' otherwise. (The
actual operator is 'operator void*() const' or something like that,
since that prevents someone from accidentally saying, for example, 'int
x = cin').

So basically what happens is this: the expression 'foo(a, b)' is just
like 'String("xyz")': it constructs a temporary, unnamed object of class
'foo' by storing the values 'a' and 'b' within the object itself. Then
that function-like object is converted to an int, char, float, double,
or String, and the conversion is what triggers the right 'operator
<type>()' function to be called. Finally the 'foo' object is
destructed, which in this case does nothing.


>In which 
>case, that's quite ingenious. I'm not sure it would prove regularly 
>useful though - seems too roundabout a solution except in quite 
>specific instances.

It's an *idiom*. Idioms are always more roundabout than features that
are directly supported in the language proper. But there's no need for
a language feature since there's an easy to use idiom that does the job
quite well.

For example, there was talk in the C++ committee of adding a new keyword
'inherited' or 'super', so that single-inheritance hierarchies, which
are quite common, could access their base class's stuff without having
to name the base class explicitly. In other words, if class 'Der' is
derived from class 'Base', and 'Der::f()' wants to call 'Base::f()', it
would be nice if 'Der::f()' could say 'super::f()' or 'inherited::f()'
instead of having to name the base class explictily, e.g., 'Base::f()',
since 'super::f()' would reduce cut-and-paste errors when copying from a
different class, and is generally simpler.

However someone mentioned an idiom that allows programmers to do just
that, and the proposal/discussion died instantly. The idea is for 'Der'
to add this line:

class Der : public Base {
typedef Base super; <===***
public:
...
};

That's more roundabout, but it gets the job done. Same with my
"overloaded return type idiom."


>>>I personally would probably have had it use static typing when it
>>>could, but when the compiler didn't know it would complain unless you
>>> added a modifier to say it was a dynamic cast - then the check gets
>>>delayed till run time. As it happens, surely that's happened anyway
>>>(albeit relatively recently) with dynamic_cast<>().
>>>
>>>My point is, it could have been made possible to utilise the best of
>>>both worlds but with a bias toward static typing.
>>
>>I think your goal is admirable. However if you think a little deeper
>>about how this would actually get implemented, you would see it would
>>cause C++ to run much slower than the worst Smalltalk implementation,
>>and to generate huge piles of code for even trivial functions. E.g.,
>>consider:
>>
>> void foo(QString& a, QString& b)
>> {
>> a = "xyz" + b;
>> }
>>
>>Pretend QString's has a typical 'operator+' that is a non-member
>>function (possibly a 'friend' of QString). It needs to be a
>>non-member function to make the above legal. Pretend the signature of
>>this 'operator+' is typical:
>>
>> QString operator+ (const QString& x, const QString& y);
>>
>>Thus the 'foo()' function simply promotes "xyz" to QString (via a
>>QString ctor), calls the operator+ function, uses QString's assignment
>>operator to copy the result, then destructs the temporary QString.
>
>That's how it works currently, yes.
>
>>However if your relaxed rules above let someone pass things that are
>>not a QString (or one of its derived classes) for 'a' and/or 'b',
>>things are much worse. (And, unfortunately, if your relaxed rules do
>>not allow this, then I don't think you're getting much if any
>>advantage to your relaxed rules.)
>>
>>In particular, if 'a' and/or 'b' might not be QString objects, the
>>compiler would need to generate code that checked, at run-time, if
>>there exists any 'operator+' that can take a 'char*' and whatever is
>>the type of 'a' (which it won't know until run-time). Not finding
>>one, it would search for valid pointer conversions on the left, e.g.,
>>'const char*', 'void*', 'const void*'. Not finding any of those, it
>>would also search for any 'operator+' that takes the type of 'b' on
>>the right. Finally, if we assume 'b' actually is a QString, it would
>>find a match since it could promote the type of 'b' from 'QString&' to
>>'const QString&' (that's called a cv-conversion).
>
>Firstly, I was thinking that the compiler would produce an error 
>without a special keyword which limits the overall possibilities of 
>casting ie; a strong hint to limit the total number of varieties. 
>Hence then much of the above searching is unnecessary.

I may not understand what exactly you were thinking for your special
keyword, but was assuming you would want to add it at the point where
the funny cast was made, not in the declaration of the function proper.
In other words, when you want an Xyz* to point at a Pqr object, and when
those types are not related through inheritance, then at that very
instant you need to tell the compiler, "I know what I'm doing, it's
okay, this pointer will simply use more dynamic type-checking."

If my assumption was similar to your thinking, then the function itself
would have no way of knowing whether it was being called by one of those
funny pointers/references, or a normal pointer/reference. In fact, if
it was called from 10 places, it might be a mixture of the two. That's
why I thought the function would need to make all those extra checks.

The more I think about it, the more I think my way is the most intuitive
way. It wouldn't even make sense to decorate the function itself, after
all, what criteria could a programmer ever use when creating a function
for knowing whether to add the keyword that says, "This parameter is
allowed to be something wildly different, in which case we'll use
dynamic type checking on this parameter." I honestly don't think I
myself could know when to use that keyword and when not (if the keyword
decorated either the function as a whole or the individual parameters of
a function).

What if my little foo() function is called from 10 places and only one
passes a reference that needs dynamic type-checking? Do we use it then?
Seems to me that we have to. But what if all 10 use the normal C++
approach, then someday someone comes along and wants to do something
dynamic. Must we really go back and change the function's declaration?
What if the function is shipped in a library from a third party? Should
they use the "dynamic type checking" keyword "just in case"?

Again, I may be wrong about what you were thinking. But I *bet* if you
forced the keyword to go into the function declaration itself, then
there would be all sorts of other problems.

Oh yea, what about function pointers? Function pointers have *types*.
If you put the keyword in the function or parameter list, then you
presumably have to change the *type* of the function pointer, otherwise
someone could accidentally have a static-typing-only function-pointer
that pointed at a dynamically-typed function, and vice versa. Would
that cause problems?

And if the function itself or its parameters are decorated, what about
allowing function overloading based on that tag? E.g., what if you
wanted two foo() functions, one that takes a statically-typed QString&
and the other takes a dynamically-typed QString&.

And what about when the 'this' pointer itself is supposed to be
dynamically typed? Do we add another keyword out where 'const' goes?
(After the paramter list?) And would it be possible to have method
overloading based on that just like we can have method overloading based
on const?

(Some of these are very subtle and require a lot of compromise, and all
you need is for one of these to "not work out right" and you have a
worse wart than we already have. Adding language features like this is
much harder than it looks, as I'm sure you know.)


>>However it's not done yet. To make the only candidate 'operator+'
>>work, it has to try to convert the left-hand parameter from 'char*' to
>>whatever is on the left-side of the 'operator+' (which it would
>>discover at run-time to be 'const QString&'). Eventually it will
>>discover this can be done in three distinct steps: promote the 'char*'
>>to 'const char*', call the QString ctor that takes a 'const char*',
>>then bind a 'const QString&' to the temporary QString object. Now if
>>finally has enough information to call 'operator+'.
>>
>>But it's still not done, since it then has to perform even more steps
>>searching for an appropriate assignment operator. (Etc., etc.)
>>
>>BTW, I've greatly simplified the actual process for function and
>>operator overloading. In reality, the compiler (and, under your
>>scheme, the run-time system) is required to find *all* candidate
>>operators that can possibly match the left-hand-side, and all that can
>>possibly match the right-hand-side, then union them and get exactly
>>one final match (there's some paring down as well; I don't remember
>>right now). The point is that it's nasty hard, and will require a
>>nasty amount of code.
>
>I think actually your point is that doing this requires duplication 
>of effort - compile-time and run-time and the two don't quite mesh 
>together perfectly.

Yes, duplication of effort. And duplication of code-size, which means a
little function could grow huge since it has to do almost as much work
as the compiler had to do when compiling the thing. And duplication of
CPU cycles, which means a little function will go much, much, much
slower.

I think it would be tantamount to running C++ as a language that
interprets its expressions every time control passes over them. (Much
slower than, say, Java, since that typically compiles things down to a
reasonably fast byte-code.)


>Ok, fair enough. Still, out of the OO languages I know, they seem to 
>strongly tend towards either static or dynamic with no attempts to 
>run a middle route. I probably am saying this out of ignorance 
>though.

There certainly aren't *many* in the middle. CLOS (Common Lisp Object
System) was one.

BTW want to talk about what's missing from C++ and Java and the rest,
CLOS has something very slick called "multi methods." Basic ideas is
this: in C++, the expression 'a.f(b)' uses dynamic binding on the type
of object referred to by 'a', but *not* based on the type of object
referred to by 'b'. This is another assymetry: why is the 'this' object
"special" in that way?

There really is no good answer, and CLOS solved it by saying you could
use dynamic binding on both (or "all N") parameters, not just the 'this'
paramter. For example, consider a hierarchy of Number, including
Integer, Double, Rational, InfinitePrecisionReal, BigNum, etc.
Everything seems great until you try to define "multiply." There are N
classes, so there are O(N^2) different algorithms, e.g., Integer*Double
uses different binary code from Rational*Integer, etc. And how to you
dispatch dynamically on those N^2 algorithms? You can't use
'a.multiplyBy(b)' since that will dynamically dispatch based on the type
of 'a' alone: there are only N different choices.

CLOS had a direct solution: define your N*N functions, and let CLOS
figure it out at runtime. It wasn't super fast, and the rules were
pretty involved (e.g., in tall hierarchies, you can have "close" and
"not so close" matches; what if one of your functions matches parameter
#1 closely and #2 not so close, and another function is the opposite;
which do you choose?) But it worked, and it was useful, at least in
some cases.

Here's another motivating example: suppose you had a hierarchy of N
Shapes, and you wanted to define a method called "equivalent()", e.g.,
'a.equivalent(b)'. The meaning of 'equivalent' was that the shapes
*appeared* the same. That means an Ellipse could be equivalent to a
Circle, but not to a Rectangle (except when both are zero-sized). A
Polygon could be equivalent to a Square or Triangle, etc. Seems
reasonable until you actually try to write that sucker. How can you get
N*N algorithms if you can only dispatch on the object to the left of the
".".

If you're interested, I can show you another idiom (emphasis) that lets
you do this in C++ and Java.


>>C++ is not D = we can't add rules that cause legal C programs to
>>generate compile errors unless there is a compelling reason to do so.
>
>I'm not seeing that this would.
>
>>What would happen with this:
>>
>> void foo(char* dest, const char* src)
>> {
>> strcpy(dest, src);
>> }
>>
>>Or even the simple hello-world from K&R:
>>
>> int main()
>> {
>> printf("Hello world!\n");
>> return 0;
>> }
>>
>>Would those generate an error message ("No version of
>>'strcpy()'/'printf()' returns 'void'")?
>
>Only if there is another overload. If there's one and one only 
>strcpy(), it gets called irrespective of return just like C. 

FYI there are two 'strcpy()'s - one with a const and one without.

>If 
>there's more than one, it uses the void return otherwise it generates 
>an error (without a cast).
>
>>* If they would cause an error, we break too much C.
>>* If they don't cause an error, we jump from the frying pan into the
>>fire: if someone later on created a version of those functions that
>>overloaded by return type, all those calls would break because
>>suddenly they'd all start generating error messages ("missing
>>return-type cast" or something like that). In other words, the
>>programmer would have to go back through and cast the return type,
>>e.g., (int)printf(...) or (char*)strcpy(...).
>
>No I think my solution preserves existing code.

I don't think so. I'll explain my second bullet with an example.
Suppose you have a function
int foo(int x);

Someone writes a million lines of code using foo(int), and a lot of the
time they ignore the return value, e.g., like how most people call
'printf()'.
foo(42);

Then later someone creates this function:
double foo(int x);

I believe your rules cause all those calls to
foo(42);
to generate an error message.


>>Adding a return-type-overloaded function wouldn't *always* cause an
>>error message, since sometimes it would be worse - it would silently
>>change the meaning of the above code. E.g., if someone created a
>>'void' version of printf() or strcpy(), the above code would silently
>>change meaning from (int)printf(const char*,...) to a totally
>>different function: (void)printf(const char*,...).
>
>In this particular case, yes. I would have the message "demons abound 
>here" stamped in red ink on that. My point is that C and C++ put lots 
>of power into the hands of the programmer anyway, so I don't think 
>the fact you can break lots of code by introducing a void return 
>variant of an existing function is all that bad. There are worse 
>potentials for error in the language.

I suppose you're right about the power-in-the-programmers-hand part.
After all, we're not talking about a programming language that
*prevents* idiots from shooting themselves in the foot!! If anything,
it separates the men from the boys. Perhaps not as bad as juggling
chain-saws, but it certainly has its "sharp pointy things" that will
make your program bleed if you screw up.


>>>Of course, that was then and this is now, but he didn't seem to me to
>>> write in an overly clear style. Quite laden with technogrammar.
>>
>>D&E (as the book is affectionally called) is a valuable resource for
>>someone like you, since it explains why things are the way they are.
>>It's probably not as hard to read as The C++ Programming Language
>>since it's really a narrative or story of how Bjarne made his
>>decisions and why. But even if it is hard to read, you still might
>>like it. (Obviously if you have only one book to buy, buy mine, not
>>his! :-) (Actually I get only a buck per book so I really have almost
>>no incentive to hawk the thing.)
>
>I'm guessing you get a 10% commission then, halved between the two of 
>you. Yeah, it's not a lot ...

Actually I don't even think it's 10%. It might be a buck a book
*divided* between us. I really don't know (and obviously don't pay much
attention to it). They send me a check every once in a while, but it's
not enough to send the kids to college so it doesn't really hit my radar
screen.


>>>I'm afraid I don't. In your 20 derived classes, each is in fact its
>>>own autonomous data processor whose only commonality is that they
>>>share an API. The API is good for the programmer, but doesn't help
>>>the data processing one jot.
>>
>>I have no idea what I was thinking above - the logic seems to totally
>>escape me. Perhaps I was referring to your last sentence only, that
>>is, to base your design totally around data. Yea, that's what I was
>>thinking. Okay, I think I can explain it.
>>
>>In my base class 'Foo', the design of the system was based around
>>'Foo' itself and the API specified by Foo. 99% of the system used
>>'Foo&' or 'Foo*', and only a small percent of the code actually knew
>>anything about the data, since the data was held in the derived
>>classes and 99% of the system was ignorant of those. In fact, there
>>are 20 *different* data structures, one each in the 20 derived
>>classes, and "99% of the system" is ignorant of all 20.
>>
>>The point is the vast majority of the code (say 99%) doesn't have the
>>slightest clue about the data. To me, that means the code was
>>organized *not* around the data. The benefit of this is pluggability,
>>extensibility, and flexibility, since one can add or change a derived
>>class without breaking any of the 99%.
>>
>>I'm still not sure that addresses what you were saying, but at least I
>>understand what I was trying to say last night.
>
>No that addresses programmability and maintainability. It does not 
>address program efficiency, which was my point.

Well I for one use flexibility as a performance tuning technique. At
least sometimes. In other words, I bury data structures in derived
classes, and sometimes end up selecting derived classes based on
performance considerations. For example, "This Bag is good for big
piles of data and has good average cost, but is occasionally individual
queries go really slow; this other one never has any really slow look-up
costs, but its average case is a little worse; this third one is the
fastest choice if you have less than 10 elements; etc." That way I can
pick and choose based on what each individual Bag (or whatever)
needs/wants/has. And the 99% of the system is ignorant of which kind of
Bag it's working with - it just knows it's using the Bag abstraction.

That's quite different from the old Abstract Data Type (ADT) idea. Both
ADTs and the thing I just described hide the detailed data structure
from the client, but with ADTs there was exactly one data structure and
with the thing I'm talking about, every individual location in the
source code that creates a Bag object could conceivably use a different
derived class == a different data structure, and the 99% of the system
would work with all these different data structurse (more or less)
simultaneously.


>>>Hence my view that OO is good for organising source (intuitively it
>>>produces good source organisation) but poor for program design (ok,
>>>program algorithms in your terms).
>>
>>I think OO has one bullet in its gun: it is good for achieving
>>non-functional goals, like extensibility, flexibility, etc. If you
>>are *very* careful, you can achieve those non-functional goals without
>>sacrificing other non-functionals, such as speed. I think if someone
>>has a program with no extensibility and no flexibility goals, then OO
>>adds little genuine value. 
>
>Err, does this mean you are agreeing with me? :)

Depends on what I'm agreeing to!! :-)
That OO is imperfect? Yes.
That OO isn't the best choice for every task? Yes.
That OO's imperfections means it is bad? No.

I think imperfect tools are good since there are no alternatives. In
other words, I think *all* tools are imperfect, and that *none* of them
pass the one-size-fits-all test. OO (and your data-oriented approach)
included.


>>>My fundamental point is that I think that you have integrated many
>>>beneficial and good programming practices into your internal
>>>conceptualisation of what OO is and means, and you are having
>>>difficulty separating them and treating them as what they are. I
>>>personally prefer to treat these things more seperately as I believe
>>>it offers me a great selection of tools from the toolbox as it were,
>>>but it's entirely a personal choice.
>>
>>You're probably right. I'm not an advocate for any given style of
>>programming, since any advocate for anything ends up being a
>>one-trick-pony, and they can only be radically successful if their
>>particular "trick" happens to be a really good fit for the project du
>>jour. Instead I try to advocate success over all, and that means
>>intentionally using whatever styles help achieve that success.
>
>Ah, agreement also. Good.

Perhaps. But you seem to be more of an advocate than me. (Meaning you
seem to be an advocate for the data-oriented approach more than I am for
OO or anything else.) But I guess it's okay for you to be an advocate,
after all, you're actually trying to convince other people that your
thing is good and they should embrace it. I, on the other hand, have
the luxury of floating above that - I don't need to promote any
technology, and therefore I "get" to be agnostic - to promote
business-level goals like "success" or whatever. You can (and should)
also use those terms, but what you really end up doing is saying,
"You'll be more successful using *my* thingy."

Naturally any decision-maker knows to listen to the guy who's not
selling anything, which is why consultants like me try to be
technology-neutral.


>>>Why isn't it a better one-size-fits-all approach? 
>>
>>Because there is no one-size-fits-all approach! :-)
>
>Ok, how about a better starting approach?

You misunderstand me. It *can't* be a better starting approach than
what I start with, since what I start with is a question-mark. In other
words, I don't start with OO and then move on from there. I start by
examining the business situation.

Example, there's a telecom company in town called White Rock Networks.
The CEO lives down the street - I pass his house all the time. He and I
and our wives went to the Symphony together a while back. His company
uses C and is afraid of C++ because of performance considerations. I
think they're wrong, but I don't care, since the fact that they have 150
C programmers who hate C++ and Java means that the *best* language for
them is C. It doesn't matter whether their reason for hating C++ or
Java is right or wrong; it only matters *that* they hate C++ and Java,
and therefore trying to get them to program in C++ or Java would cause
the best of them to jump ship - to quit and move to a different company.

I do the same with programming approaches, e.g., structured programming
vs. object-based vs. full object-oriented vs. this thing you're cooking
up. I'd like to learn more about your thing so I can use it someday,
but ultimately I'll need to find a business and technical spot where
it's a good fit.


>>>Surely you would 
>>>agree that if you base your design on quantities of data and the
>>>overheads of the media in which they reside, you naturally and
>>>intuitively produce a much more efficient design?

I honestly don't know enough about what you're doing to agree or
disagree.

***HOWEVER*** even if I agreed fully with that statement, I still don't
think it has much to do with whether your stuff should be used on a
given project. I honestly believe language- and technique-selection
should be based on things like who the programmers are, what they know,
whether the universities are churning out more programmers for us,
whether we're going to be able to replace the ones we have if they quit,
etc., in addition to the technical aspects you mentioned above. Just
because "X" is a better mousetrap than "Y" doesn't mean we should use
"X". We should use "X" if and only if "X" will reduce the overall
company time, cost, and risk. And that includes the time and cost for
retraining, the risk of losing the people we have who don't make the
transition, and the risk of being held hostage by our programmers (e.g.,
if we choose a technology where there are only a few competent
programmers, we might end up having to pay through the nose just to keep
the ones we have).


>>Even if what you're saying is true, "a much more efficient design"
>>might not the top priority on "this" project. All I'm saying is: I
>>prefer to start with the goals, *then* decide which technologies to
>>use. Anyone who comes in talking, who already knows which
>>technologies should be used before understanding the goals, is foolish
>>in my book.
>>
>>I wouldn't want to assume your data-oriented approach is the answer
>>any more than I would want to assume OO is the answer. First tell me
>>what the question is, *THEN* I'll come up with the "most appropriate"
>>answer.
>
>Ok, I think we're escaping the fundamental core of this thread. 
>Basically, what I am saying, is that across all the software projects 
>in all the world, people are mostly applying an OO-based solution as 
>a primary leader. I feel this produces worse quality software because 
>of the problems with lack of intuition

Which may be true. But the fact that "people are mostly applying an
OO-based solution as a primary leader" means there are a lot of
programmers out there, and there will be a lot of tool vendors and
compiler vendors to choose from, and we'll be able to get off-the-shelf
libraries, and we'll be able to get consultants to help out in a pinch,
and we'll have the choice whether to rent or buy our programmers, and,
and, and.

Actually I really like your spunk and determination, and I really
shouldn't try to throw a wet towel on your fire. You *need* your fire
since otherwise you won't be able to finish what you've started.

Tell you what: let's not talk about being language neutral any more,
since it will not help you. Instead, please tell me about your
paradigm. Show me some examples.

I really need to get some sleep - sorry I can't finish these responses.

Marshall

PS:
>A question your expertise may be able to answer: is there a non-GPL 
>portable Unix shell because I've looked *everywhere* and can't find 
>one?

Sorry, don't know.



From: Niall Douglas <[email protected]>
To: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Sun, 4 Aug 2002 18:49:45 +0200

On 3 Aug 2002 at 3:28, Marshall Cline wrote:

> >>This proper-inheritance notion is the same as require-no-more,
> >>promise-no-less, which you basically didn't like :-(
> >
> >No, I didn't like the /phrase/, not its meaning. I understood the
> >meaning three emails ago.
> 
> Regarding "three emails ago," we seem to have had a small
> communication problem. I re-explained things after you already "got
> it," and I apologize for frustrating you that way. I certainly did
> not want to imply you are stupid or thick-headed or something, since
> it is quite clear (to me, anyway) that you are not.

Oh good. I'm glad you don't think me stupid - many others do. I put 
it down to personality incompatibilities.

> However I think we both played a part in this communication problem.
> For example, when I first explained the "require no more and promise
> no less" idea in my previous email, you replied, "This is quite
> ephemeral and subtle..." Although it is clearly subtle at times, I
> see it as the opposite of ephemeral, and since, perhaps as a
> back-handed compliment to you (e.g., "I *know* this guy is bright, so
> if he thinks it is ephemeral, I must not have explained it very
> well"), I re-explained it.

In which case, I must explain myself as well - why I said that in 
that fashion wasn't just because of you, but also for the benefit of 
the others following this conversation. I *do* however think it 
subtle because, like you said before, it's not a programming error.

> There have been several other times throughout our conversation when
> you said something that made me think, "He still doesn't see what I'm
> seeing." I see now I was wrong, so I'm not trying to convince you
> that you don't get it. I'm simply trying to help you see why I
> re-explained things too many times.

Well, my father will often tell me the same story maybe three or four 
times before I point out to him he's already told me three or four 
times. Also, given some of the people I associated with at university 
who I shall describe as following alternative lifestyles, quite 
severe weirdness is something I'm used to.

> For example, when you explained that you had already checked to make
> sure 'prepend()' and 'append()' were not called within the base
> class's code, and that that gave you confidence there wouldn't be any
> errors resulting from your redefining those methods in TSortedList, I
> thought to myself, "He doesn't get it yet; checking the base class
> itself is necessary but not sufficient." So I (erroneously) explained
> it again.

No, I was referring to before when I had ever talked to you (which I 
thought I made clear at the time). In current code, TSortedList isn't 
even related to QList anymore to partly fix the issues you 
illustrated to me.

> Put it this way: stupid people say random things. They are incapable
> of coming up with a cohesive perspective on complex things, so their
> statements are often inconsistent with each other. You said some
> things that (I thought!) were inconsistent with each other, but you're
> not stupid (or if you are, you sure fooled me ;-) If I had thought
> you were stupid, I probably would have politely ended the conversation
> and quietly written you off as a lost cause (sorry if that is
> condescending, but we both have better things to do with our lives
> than pour hours and hours into people we can't actually help). So
> instead of writing you off, I figured, "Just one more email and he'll
> *really* get it!"
> 
> Hopefully that explains why (I believe) we both played a role in me
> being a broken-record. And also, hopefully it shows you that I didn't
> repeat myself because I thought you were dumb, or because I was dumb,
> but instead because I thought you were bright enough to get it, and if
> you saw it from just one more vantage point, you'd get it.
> 
> Okay, so you get it. Now we can move on!

I should mention you've caused me to go off and rewrite quite a lot 
of classes which has then caused quite a lot of code to break which I 
think is the best possible proof of my understanding. For example, my 
security descriptor class I had implemented as a public derivation of 
QList which is now clearly bad. I've since made it private 
inheritence with selected methods made public with "using" (a full 
reimplementation of that class would basically mean a start from 
scratch given how often it is used - so I'm afraid I compromised). I 
also fixed up the TKernelPath class which is a string with extra 
bells and whistles - previously I had changed some methods to do 
different behaviour, now I've reverted them and put the new behaviour 
in new methods. And etc. etc. it goes on and on ...

I do want to make it clear though that I am very grateful for the 
tips. You have saved me lots of time in the long run.

> [minimising ripple effect]
> >That's an admirable desire, but do you think it's really possible?
> 
> Yes, in the sense that I've seen big projects get 80% or 90% of the
> way there. The key, of course, is to find low-budget ways to get the
> first 80% of the value, and given the 80/20 rule, that turns out to be
> possible. I call it the low-hanging fruit. It's not perfect, but
> it's much better than if we go with the status quo.
> 
> The low-hanging fruit involves just a few disciplines, including
> "programming by contract" (you may be familiar with this; if not,
> sneak a peek at chapters 1-4 of Bertrand Meyers's book,
> Object-Oriented Software Construction; or I could explain it to you)
> and it requires design reviews where the contracts in base classes are
> carefully evaluated based on some pretty straightforward criteria. 
> Since many programmers don't have self-discipline, even when it would
> be in their own best interest, project leaders must enforce the above
> by putting them into the project's process. In the end, these things
> actually will happen if they get tracked and reviewed (read
> "enforced") by management, and they really do contribute a great deal
> toward the above goal.

Ah, you mean to write out a set of rules which all programmers must 
follow to the letter, and I do spot checks to ensure they're doing 
it. I actually came up with that on my own too - I made the basic 
inviable on pain of death rules into a half A4 sheet and then 
explanations why for another fifteen pages. I then stuck the half A4 
sheet on the monitor of every programmer and got very annoyed if they 
removed it :)

> There are a few other design and programming ideas I use to help
> achieve that goal (more low-hanging fruit). For example, simple (but
> often overlooked) things like creating an explicit architecture with
> explicit APIs in each subsystem, wrapping the API of each OO subsystem
> so the other subsystems can't "see" the OO subsystem's inheritances or
> how it allocates methods to objects (in my lingo, so they can't see
> it's design), etc.

Surely that's a technique since C days where you abstracted all the 
implementation detail into the .c file? Well, not quite the same as 
the header file space tends to be unified, but I get what you mean. 
Again, you're lessening coupling.

> >If 
> >I've learned anything from quantum mechanics and biology, it's that
> >there will *always* be knock-on effects from even the tiniest change
> >in any large system. Good design and coding is about minimising
> >those, 
> 
> Agreed: bad design has a huge ripple effect, which can be thought of
> as chaos (a tiny change in one places causes a very large change
> somewhere else).

That's another reason behind my project BTW - I'm heavily deriving my 
design from quantum theory. Basically, in the quantum world 
everything exists because it has a relation to something else and yet 
order and self-containment emerges easily.

Go further: in the human brain, there are billions of interconnected 
nodes all talking to each other. You can kill or interfere with a 
substantial number of those and yet everything automatically adapts. 
I could go now into fractals and non-linear math examples, but I 
think you get the idea.

Basically, my project aims to make all the components tiny because in 
theory the total cost of changing a part of the overall system should 
rapidly decrease. In theory, you should be able to modify or 
interfere with substantial parts of the web with merely a lessening 
of functionality, not a complete stop. Obviously, this is some 
decades away yet, but my project is the first step down that road.

> >>I rather like that idea since I have found the average programmer
> >>is, well, average, and is typically unable or unwilling to
> >>understand the whole. That means they screw up systems where the
> >>whole is bigger than the sum of the parts -- they screw up systems
> >>where they must understand a whole bunch of code in order to fully
> >>know whether a change will break anything else.
> >
> >Hence the usefulness of pairing programmers.
> 
> Perhaps you're right. I always worry about know-it-all programmers
> who insist on their own way even when they're wrong, but like I said,
> that's more of a theoretical concern about pair programming since I
> haven't experienced it in practice.

Psychology theory tells us people are much less likely to 
individualise when they have someone looking over their shoulder. 
Pair programming should theroretically cause a lazy programmer to 
sharpen up.

However (and I did spend some time talking with the webmaster at 
www.extremeprogramming.org about this), my experience is that certain 
types of personality form a lazy pair who spend all of their time 
talking and mucking around. Furthermore, there are programmers I know 
of who would hate being paired (they're the same ones who hate people 
looking over their shoulders) and of course there are also people who 
just dislike each other. Lastly, I have difficulty imagining myself 
being paired - at school, when we had to go to these programming 
classes, I always raced ahead of the guy with me - I did try to hold 
his hand, but he really wanted to defer to me. I can definitely see 
that unequally skilled programmers wouldn't pair either - one would 
defer everything to the other.

> [about motivation your programming team]
> Perhaps that success has been because I usually present some of these
> ideas in front of a group/seminar, and I usually (verbally) beat my
> chest and say something like, "Anybody who hurts their company to
> benefit themselves is unprofessional and deserves to be fired."
> Basically I shame the hot-shots into realizing they can't hold the
> company hostage just for their own personal job security. In fact,
> I'll be giving that sort of speech at a development lab of UPS on
> Tuesday, and I'll probably mention the recent corporate scandals. If
> I do, I'll talk about those disreputable people who hurt the
> stockholders in order to line their own pockets. Nobody wants to be
> compared to those guys, which brings out a righteous streak in
> everybody (including the hot-shots).

That's a very US attitude about business being somehow holdable to 
some higher standard. I mean, in Europe it's naturally accepted every 
company will do the most evil possible to increase profits - hence 
our very red-tape heavy legistlature. But then of course, we're 
comfortable with Marx here.

> (Sometimes, when I really need to bring out a big hammer, I compare
> the "wrong" attitude to terrorism. It's a stretch, but I'm pretty
> good in front of a crowd. The key insight is that nobody should be
> allowed to hurt their company just to help themselves. For example, I
> say something like, "If you hold company assets in between your
> earlobes, they don't belong to you - they belong to the company. If
> you refuse to write them down just to protect your own job, you're no
> better than a terrorist, since you're basically saying, "Give me my
> way or I'll hurt you." Amazingly I haven't been lynched yet.)

Again, the word "terrorism" doesn't carry anything like the same 
weight here (sometimes I must admit to laughing at CNN's import of 
gravitas on it). We tend here to look at terrorism in its context, 
not its acts - after all, modern terrorism was invented by European 
colonialism and governments as well as NGO's have used it frequently 
in both internal and external causes. For example, the IRA are 
freedom fighters to many but terrorists to the English. All the great 
revolutionary leaders I was taught about in school were so highly 
held by massacring every important Englishman to set food in Ireland, 
along with their wives and children (it sent a "better" message). 
They all went on to become government ministers and highly respected 
internationally, including by the English.

Anyway, I digress. I get quite annoyed by the actions of the current 
Bush administration :(

BTW, if you want to improve your performance in front of a crowd, 
studying debating techniques or even toastmastering is very 
worthwhile. You can pick up all the techniques professional 
politicians use.

> Like I said, I doubt any of this saber-rattling actually changes
> people's hearts, and therefore it won't change the guy who gives 8
> hours notice before vacation, but it does seem to force the hot-shots
> into supporting the plan, and invariably they buy-in.

No, but psychologically it reduces the individual's ability to 
rationalise non-conformance ie; make excuses for not toeing the line.

> You're probably right about what you're saying, but that's a totally
> different topic from what I was *trying* to say. I'm not talking
> about productivity differences (either between those who use assembler
> vs. high-level tools or between the hot-shots and the dolts). I'm
> talking about the ability to understand the whole ripple effect of a
> change in their heads. In other words, the *average* architect is
> broad but shallow (they know a little about the whole system, but
> often don't know enough to reach in and change the code), and the
> *average* "coder" is deep but narrow (they have deep understanding of
> a few areas within the system, but most can't tell you how all the
> pieces hold together - they don't have the breadth of an architect). 

Well, to most software engineers their job is to provide money - they 
don't do it at home for fun.

> But knowing the full ripple effect of a change to the system often
> requires both broad and deep knowledge that very few possess. In one
> of my consulting gigs, there was a guy (Mike Corrigan) who was both
> broad and deep. Management called him a "system-wide expert." 
> Everyone believed he could visualize the entire system in his head,
> and it seemed to be true. It was a huge system (around two million
> lines below a major interface, and around 14 million lines above
> that). When someone would propose a change, he would go into
> never-never land, and perhaps a week later he would explain why it
> couldn't be done or would provide a list of a dozen subsystems that
> would be effected.

Ah yes, the sudden revelations you have while in the shower. You give 
yourself a task to think about, then carry a little notebook 
everywhere to go to write down ideas which suddenly pop into your 
head. You can get remarkable results using this technique.

> When I wrote the above, I was visualizing the Mike Corrigans of the
> world. After they get enough experience with a system, they are both
> deep and broad, and in particular, they can see the entire ripple
> effect in their heads. That's what I was trying to say (though you
> can't see the connection without seeing the paragraph prior to the
> paragraph that begins, "If we don't solve the middle-of-the-bell-curve
> problem...")

Of course, the above type of guy is worth their weight in gold and 
ultimately, their use cannot be completely eliminated. But it can be 
minimised, and more importantly if he were to leave/retire, the 
ensuing chaos can also be minimised.

> Perhaps you would agree with this: In most companies, those in the
> middle-of-the-bell-curve have a difficult time reliably changing large
> systems. Companies that solve this problem will be better off.

Yes, absolutely.

> >I'll just mention RISC-OS had fantastic documentation (even with
> >custom designed manuals which automatically perched on your lap).
> >It's a difference I still miss today, and it's why my project has
> >excellent documentation (I wrote a lot of it before the code).
> 
> Bingo. Good stuff. I do the same thing: write the specs as best I
> can ahead of time, and invariably add to them as I get further into
> the project, and sometimes (carefully) changing what I wrote when it
> was wrong or inconsistent (or when I simply discover a better way).

Heh, definitely the story of this project. I was of course severely 
limited because I didn't know what C++ would let me or not let me do. 
But still, so far, it's been all detail changes eg; my TProcess class 
had to be made abstract because the kernel and client libraries share 
99% of the same code so I passed all the differences out to base 
classes.

> When "selling" companies on this idea, I even use a different term
> than documentation, since most people think of documentation as
> something you write that *describes* what the code already does. But
> these contracts/specifications *prescribe* what the code *should* do,
> hence the different name: contracts or specifications.

Good idea. I usually use the word "specification" myself, but I could 
use the concept of contracts to obtain for myself more design time. 
Companies generally like to see you start coding immediately - I 
usually do grunt work initially into the coding period, leaving me 
more subconcious time to think of problems with the design.

> Generally speaking, good C++ (or Java or Eiffel) class hierarchies are
> short and fat with very little data in the base classes. The
> fundamental reason for this is that these languages exhibit an
> assymetry between data and code. In particular, they let a derived
> class replace some code in the base class (virtual functions), but
> they don't let a derived class do the same with data (they don't have
> "virtual data"). Once a base class has a certain data structure, all
> derived classes forever and ever are forced to have that data
> structure. If a given derived class doesn't "want" that data
> structure, it has to carry it around anyhow, and that typically makes
> it bigger or slower than optimal.

Hmm. I'm not seeing a need for virtual data given you could use a 
virtual method to implement the virtual data. However, that's an 
idiom.

> Obviously this is backwards semantically, and is therefore bad based
> on everything we've discussed forever (which you already get; I know).
> But it is interesting how the "inheritance is for reuse" idea pushes
> you in exactly the wrong direction. It is also interesting (and
> somewhat sad) that the proper use of inheritance ends up requiring you
> to write more code. That would be solvable if today's OO languages
> had virtual data, but I'm not even sure how that could be implemented
> in a typesafe language.

I don't think it possible in a statically typed language, but it 
could be done in a dynamically typed language. Effectively, it would 
be an extension of the dynamic type system.

> I actually do know of another way to have-your-cake-and-eat-it-too in
> the above situation. Too late to describe this time; ask me someday
> if you're interested. (And, strangely enough, although C++ and Java
> and Eiffel don't support this better approach, OO COBOL of all
> languages does. So if anybody ever asks you which is the most
> advanced OO language, tell them COBOL. And snicker with an evil,
> Boris Karlov-like laugh.)

I think I may be ferociously set upon and horribly mauled if I tried 
that :)

> >>For example, they could cause the list's contents to become
> >>unsorted, and that could screw up TSortedList's binary search
> >>algorithms. The only way to insulate yourself from this is to use
> >>has-a or private/protected inheritance.
> >
> >Surely private or protected inheritance affects the subclass only?
> >ie; you could still pass the subclass to its base class?
> 
> I have no idea what you're asking - sorry, I simply can't parse it.

Actually you answered it below :)

> Here's the deal with private/protected inheritance. Where public
> inheritance "means" is-substitutable-for, private and protected
> inheritance "mean" has-a (AKA aggregation or composition). For
> example, if TSortedList privately inherited from GList, then it would
> be semantically almost the same as if TSortedList had-a GList as a
> private member. In both cases (private inheritance and has-a) the
> methods of GList (including the now infamous prepend() and append())
> would *not* automatically be accessible to users of TSortedList, and
> in both cases users of TSortedList could not pass a TSortedList to a
> function that is expecting a GList (which, as you know, is the start
> of all our "improper inheritance" problems).
> 
> There are a few differences between private inheritance and has-a, but
> the differences are buried in the code of TSortedList. In particular,
> if TSortedList privately inherited from GList, the derived class's
> code (its member and friend functions) are allowed to convert a
> TSortedList& to a GList& (and similarly for pointers), and TSortedList
> can use a special syntax to make selected public methods of GList
> public within TSortedList. For example, if there is a 'size()' method
> within GList that TSortedList wants to make public, then in the public
> section of TSortedList simply say "using GList<T>::size;". Note: the
> '()' were omitted intentionally. See the FAQ for this 'using' syntax.
> In fact, the FAQ has a whole section on private and protected
> inheritance - you might want to read it since it will solve your "bad
> inheritance" problem, at least with TSortedList.

Have read it many times. You see, in order not to appear stupid 
during these emails, I usually research every point before I make it.
Very time-consuming, but also an excellent way of learning.

> [embedded systems]
> >No, that's not hugely true anymore.
> 
> We probably have different experience bases, since most of the
> companies I've worked with have such tight coding constraints that
> bloatware like WinCE are unthinkable. The folks I've worked with try
> to squeeze the whole shebang, including OS, apps, data, drivers,
> wireless stuff, everything, into 4MB, sometimes less. They often end
> up with much smaller, proprietary OSs.

That's often because they've committed themselves to using one 
particular piece of hardware (for whatever reason). If you're talking 
sub-CE capable hardware (eg; electronic barometer etc.), then 
*currently* you're right, there is still a need for proper assembler 
hackers.

But I must point out, think what it will be like in ten years when 
Bill's vision of having every embedded system running Windows comes 
true? That day is coming, and it's coming quicker than many realise. 
Just as the US multinationals crushed Europe's indigenous desktop 
industry, they are now targeting the embedded market. The result in 
either case is vastly more powerful embedded systems, and much less 
assembler programmers.

> But in the world of, say, calculators, customers expect the thing to
> run for a year on an AA battery or two. The screens on those things
> aren't backlit, but they have oodles of code in them. I remember
> working with an HP team that said they had half a million lines of
> code in one of their calculators. It solves differential equations,
> plots various kinds of graphs, is programmable, etc., etc., and the
> amount of memory it has is important to them.

Yeah I remember talking to a guy at ARM during lunch about scientific 
calculators.

> I have a Palm V. Doesn't do too much, has a lousy 160x160 LCD
> black-and-green screen, and it came with only 4MB of memory, but it's
> tiny, inexpensive, weighs only 4(?) ounces, and runs for a week
> between charges. I could have purchased a WinCE box, but it's much
> heavier, more expensive, and needs to be charged much more often.

I would have bought a Psion Netbook if I ever had the money. They had 
a 150Mhz StrongARM with 32Mb of RAM and ran Linux (proper desktop 
version) like a dream. You could reflash them you see with any OS you 
wanted (they come with Symbian OS). Ran for 12-16 hours continuously 
before needing a recharge, despite its 640x480 full colour display.

> Now the world is slowly moving toward higher power processors and
> backlit screen, which ultimately means people will begin to expect
> less and less wrt battery life. In the mean time, there will always
> be companies like HP, Texas Instruments, UPS, FedEx, Brooklyn Union
> Gas, etc., that are up against memory limitations, and they'll always
> be fighting to add functionality without increasing hardware. I think
> that stuff is fun.

Oh it is fun. And in many ways, I wouldn't mind my old job at ARM 
back except I had no power there and unfortunately in ARM, the more 
power you have the less you program :(

> >In other words, Limbo is doing a full compile to a proper assembler
> >model (which just happens not to have a processor which can run it,
> >but one could be easily designed). Java is really mostly interpreted
> >in that the source is pretty easy to see in the byte code. I've seen
> >some reverse compilers and their output is awfully similar to the
> >original - whereas no reverse compiler would have a hope
> >reconstituting C++ (or even C).
> 
> Yes Limbo does a full compile to an abstract machine. But I don't
> agree with one of your implications: The fact that Java uncompilers do
> a pretty good job does not mean that Java is mostly interpreted. In
> fact, the real reason Java uncompilers do a pretty good job is because
> of all the "meta data" Java .class files are required to carry around
> (they have to contain enough meta data that "reflection" can work;
> e.g., Java lets you poke around in the meta data of another class,
> including determining at runtime what methods it has, the
> parameters/return types of those methods, which methods are private,
> protected, public; etc., etc.). If a Limbo binary (a .dis file)
> contained the meta-data that a Java .class file contained, a Limbo
> uncompiler could do about as good a job as a Java uncompiler.

I didn't know that. Fair enough. I'm still thinking though the Java 
bytecode was not the best design it could be though.

> Agree that it would be hard to uncompile C or C++, even if there was
> meta-data, since it would be much harder to guess what the original
> code looked like from the instruction stream. But I think that's an
> artifact of the fact that a "real" processor has very small granular
> instructions and the optimizer is expected to reorganize things to
> make them fast/small/whatever. In contrast, the byte-codes for Limbo
> and Java do more work (e.g., both have a "call" instruction that does
> a *whole* lot more than a "real" hardware call), so they expect the
> virtual machine itself to be optimized for the particular hardware
> platform.

Heh, the ARM doesn't have even a call subroutine instruction (nor a 
stack). It's proper RISC :)

> FYI Java has a number of processors that run it. In that case,
> wouldn't the assembly language look just like Java byte-codes?

I was under the impression there were processors which natively 
executes /some/ of the byte code, but a majority was too high-level 
and so remained being interpreted. ARM do just such a processor in 
fact - you tell it to go execute the Java at address X and it calls a 
vector everytime it finds something it doesn't understand. Provided a 
12x speedup if I remember (I was involved in the design).

> >although theirs merely includes windows. They're also come up 
> >remarkably with quite a few things I had thought of independently -
> >like overloading mouse button presses. I'm doing it in a way though
> >that won't scare people (unlike Plan 9)
> 
> The interesting thing to me is that Inferno is built *around* Limbo.
> For example, whereas pid in Unix is a process ID, in Inferno it's a
> thread-ID, and each thread runs a different .dis file. In other
> words, running an application in Unix forks a process with its own
> address space, but in Inferno it simply spawns a thread in the shared
> address space, and runs a .dis file along with all the other .dis
> files that are currently running.

Surely they have data consistency errors then if faulty code corrupts 
the shared data? Wouldn't be a very secure system then.

> I'm not doing it justice. I'll try again: Inferno is nothing but one
> big Limbo interpreter. Every program in Inferno, including the shell
> itself, utilities like 'ls', and all the rest, are Dis threads that
> are runing in the same address space by the same Dis "virtual
> machine." The shared address space thing seems horribly error prone,
> and perhaps it is, but you've already seen how namespaces can be
> trimmed for individual applications ("threads"), so apparently that's
> how they keep these different apps separated. It seems like a slick,
> lightweight idea.

As in, copy on write?

I should mention I didn't have much success with Plan 9. Their user 
interface is *extremely* minimalist and their documentation could be 
described as minimalist as well :(

As I've mentioned before, I really don't like anything that isn't 
intuitive. I should be able to use without a manual, but /excel/ with 
a manual.

> (BTW you mentioned Plan 9. Is that what you got? Or did you get
> Inferno? I've been discussing Inferno. I believe Inferno stole the
> namespace idea from Plan 9, but I don't know that Plan 9 is built
> around a Dis virtual machine the way Inferno is. Also I'm not sure
> about Limbo on Plan 9.)

According to Vita Nueva, Plan 9 is merely an industrial version of 
Inferno. The two will talk together via 9P and in most intents and 
purposes they're identical.

> >Maybe I should go apply for a job at Bell Labs? Nah, that US visa
> >thing getting in the way again ...
> 
> Vita Nuova is in the UK, and they own the rights to Plan 9 and
> Inferno.

Well, if I ever return to the UK, I may give them a call. They're in 
northern england as well which is a major brownie point (I can't 
stand the south - too crowded).

BTW if they own it, why is Bell still doing all the development work?

> >Thanks for pointing me towards Plan 9, I wouldn't have known my ideas
> > are so agreed upon by (eminent) others without it!
> 
> You're famous and you didn't know it.

No I'm like Leibniz who invented calculus and didn't get the credit 
for it. When I start getting invited to give speeches, *then* I'm 
famous! (Futhermore, it'd be nice to have a bit more free cash)

> Close. It's called the cast-operator. It's used in cases when you
> want to allow your object of class Foo to be converted to an object of
> class Bar. For example, the statement:

I've just used that (the cast operator) to implement a template class 
permitting thread local storage. You literally do:

TThreadLocalStorage<MyLocalData *> locdata=new MyLocalData;
...
locdata->foo=5;

> However someone mentioned an idiom that allows programmers to do just
> that, and the proposal/discussion died instantly. The idea is for
> 'Der' to add this line:
> 
> class Der : public Base {
> typedef Base super; <===***
> public:
> ...
> };
> 
> That's more roundabout, but it gets the job done. Same with my
> "overloaded return type idiom."

That's a good idea. May use that myself - I had been doing a search 
and replace within a selection.

> (Some of these are very subtle and require a lot of compromise, and
> all you need is for one of these to "not work out right" and you have
> a worse wart than we already have. Adding language features like this
> is much harder than it looks, as I'm sure you know.)

I did some investigation into points to support my arguments, but 
unfortunately found plenty of points against my argument :) - much of 
which followed what you said.

So, I relinquish my point. However, see below for my proposal for my 
own language which you got me thinking about in bed last night.

> >Ok, fair enough. Still, out of the OO languages I know, they seem to
> >strongly tend towards either static or dynamic with no attempts to
> >run a middle route. I probably am saying this out of ignorance
> >though.
> 
> There certainly aren't *many* in the middle. CLOS (Common Lisp Object
> System) was one.
> 
> BTW want to talk about what's missing from C++ and Java and the rest,
> CLOS has something very slick called "multi methods." Basic ideas is
> this: in C++, the expression 'a.f(b)' uses dynamic binding on the type
> of object referred to by 'a', but *not* based on the type of object
> referred to by 'b'. This is another assymetry: why is the 'this'
> object "special" in that way?
> 
> There really is no good answer, and CLOS solved it by saying you could
> use dynamic binding on both (or "all N") parameters, not just the
> 'this' paramter. For example, consider a hierarchy of Number,
> including Integer, Double, Rational, InfinitePrecisionReal, BigNum,
> etc. Everything seems great until you try to define "multiply." There
> are N classes, so there are O(N^2) different algorithms, e.g.,
> Integer*Double uses different binary code from Rational*Integer, etc. 
> And how to you dispatch dynamically on those N^2 algorithms? You
> can't use 'a.multiplyBy(b)' since that will dynamically dispatch based
> on the type of 'a' alone: there are only N different choices.
> 
> CLOS had a direct solution: define your N*N functions, and let CLOS
> figure it out at runtime. It wasn't super fast, and the rules were
> pretty involved (e.g., in tall hierarchies, you can have "close" and
> "not so close" matches; what if one of your functions matches
> parameter #1 closely and #2 not so close, and another function is the
> opposite; which do you choose?) But it worked, and it was useful, at
> least in some cases.

It's interesting you mention Lisp. I studied Logo while I was at 
school, became quite good at it and came fourth in an international 
competition (as usual only because my solutions were a bit whacky). 
Either way, whilst lying in bed last night I had sets floating around 
in my head and visions of a language which fufilled my three 
criteria: (i) that I program what to do not how to do it (ii) that it 
centres around data and (iii) it is compilable and interpretable, 
with the preference on the interpretable.

Basically, sets as you know are a collection of data, whether that is 
other sets, numbers, structures etc. I had a vision of basically the 
programmer preparing a set and executing code on it - with the 
interesting proviso that of course, code is data so quite 
realistically a set could be a collection of code. Of course, there 
is a kind of OO with the ability to attach code to data and arguably 
you can perform inheritence by unioning two sets of data - hence 
their code unions as well - and you could attach more code to have 
the two sets work together. However, it's definitely not pure OO.

Regarding compiling the thing, you can ask it to spit out C++ - which 
it will - and link against a run-time library. Performance would only 
be marginally better than interpreting it, but that's fine by me - I 
only want the ability to compile for those who don't like giving away 
their sources.

I looked into Lisp, and found no one has bothered doing much with it 
in five years now. Pity, because while I remember Logo didn't always 
do things as well as it could (I remember some non-intuitive syntax), 
it was pretty powerful. I'd like to do with this language the same as 
the rest of my project - is easy to use on the outside, but gets 
exponentially more powerful the deeper you go - and always always 
intuitively.

Of course, the language would directly work with my project's data in 
its unified dataspace. You merely run around connecting stuff 
together and making it go. However, I want it easily powerful enough 
you could write your entire application in it and indeed, that you 
would *want* to always write in it.

Anyway, it's not of much import. Given I can't find a shell anywhere, 
I'll have to write my own and hence I need some gameplan for design 
as it will become someday a full blown language.

> Here's another motivating example: suppose you had a hierarchy of N
> Shapes, and you wanted to define a method called "equivalent()", e.g.,
> 'a.equivalent(b)'. The meaning of 'equivalent' was that the shapes
> *appeared* the same. That means an Ellipse could be equivalent to a
> Circle, but not to a Rectangle (except when both are zero-sized). A
> Polygon could be equivalent to a Square or Triangle, etc. Seems
> reasonable until you actually try to write that sucker. How can you
> get N*N algorithms if you can only dispatch on the object to the left
> of the ".".

That's sounds horrendous. I'd personally redesign :)

> If you're interested, I can show you another idiom (emphasis) that
> lets you do this in C++ and Java.

Go on then. I've already used most of your examples in some way.

> >Only if there is another overload. If there's one and one only 
> >strcpy(), it gets called irrespective of return just like C. 
> 
> FYI there are two 'strcpy()'s - one with a const and one without.

Thought that was on its input parameter? Besides, a const <type> is 
merely a strong indicator that type's data should not be modified - 
otherwise, it acts the same. I see no difference.

> >No I think my solution preserves existing code.
> 
> I don't think so. I'll explain my second bullet with an example.
> Suppose you have a function
> int foo(int x);
> 
> Someone writes a million lines of code using foo(int), and a lot of
> the time they ignore the return value, e.g., like how most people call
> 'printf()'.
> foo(42);
> 
> Then later someone creates this function:
> double foo(int x);
> 
> I believe your rules cause all those calls to
> foo(42);
> to generate an error message.

Absolutely. But what happens currently? Currently, under C++, you get 
an error about not permitting overload based on return type.

My solution merely offers the programmer the ability to overload on 
return type. It's not without caveat nor danger, and furthermore 
probably only newly written code could use it properly - however, it 
does not break existing code *unless* the programmer does something 
stupid.

I can't see any reason why the next version of C++ shouldn't support 
this. As your idiom for overloading return types shows, it can be 
useful and certainly I would have found it useful during this 
project.

> I suppose you're right about the power-in-the-programmers-hand part.
> After all, we're not talking about a programming language that
> *prevents* idiots from shooting themselves in the foot!! If anything,
> it separates the men from the boys. Perhaps not as bad as juggling
> chain-saws, but it certainly has its "sharp pointy things" that will
> make your program bleed if you screw up.

Precisely. Given the lack of damage to existing code and the handy 
possible benefits, it should be submitted for approval IMHO.

> >Err, does this mean you are agreeing with me? :)
> 
> Depends on what I'm agreeing to!! :-)
> That OO is imperfect? Yes.
> That OO isn't the best choice for every task? Yes.
> That OO's imperfections means it is bad? No.
> 
> I think imperfect tools are good since there are no alternatives. In
> other words, I think *all* tools are imperfect, and that *none* of
> them pass the one-size-fits-all test. OO (and your data-oriented
> approach) included.

Ah good, I can fully agree with all of the above.

> >Ah, agreement also. Good.
> 
> Perhaps. But you seem to be more of an advocate than me. (Meaning
> you seem to be an advocate for the data-oriented approach more than I
> am for OO or anything else.) But I guess it's okay for you to be an
> advocate, after all, you're actually trying to convince other people
> that your thing is good and they should embrace it. I, on the other
> hand, have the luxury of floating above that - I don't need to promote
> any technology, and therefore I "get" to be agnostic - to promote
> business-level goals like "success" or whatever. You can (and should)
> also use those terms, but what you really end up doing is saying,
> "You'll be more successful using *my* thingy."
> 
> Naturally any decision-maker knows to listen to the guy who's not
> selling anything, which is why consultants like me try to be
> technology-neutral.

I suppose part of where I'm coming from is because I've read lots of 
psychology and I know there is not a single person on this planet who 
is objective. Everyone brings their prejudices and preconceptions to 
the table. In fact, I would even go so far as to say that overly 
attempting to be objective does you a lot of harm.

My view is that in order to make the best choices, one needs to 
accept ones partiality because only through that are you aware of 
your biases and thus your ability to be flexible is greatly enhanced. 
How you *handle* your prejudices is far far more important than the 
prejudice itself.

A bit of a different take I know, but I can produce studies to 
support this. Of course, eastern philosophy has taken this position 
for millennia, as indeed did the west until Descartes.

> >>>Why isn't it a better one-size-fits-all approach? 
> >>
> >>Because there is no one-size-fits-all approach! :-)
> >
> >Ok, how about a better starting approach?
> 
> You misunderstand me. It *can't* be a better starting approach than
> what I start with, since what I start with is a question-mark. In
> other words, I don't start with OO and then move on from there. I
> start by examining the business situation.

Note I don't believe anyone starts with a tabula rasa. Everyone 
brings their history of experience (it's what you're paid for!) and 
with that comes a lack of objectivity.

> It doesn't matter whether their
> reason for hating C++ or Java is right or wrong; it only matters
> *that* they hate C++ and Java, and therefore trying to get them to
> program in C++ or Java would cause the best of them to jump ship - to
> quit and move to a different company.

Good luck to them in finding pure ANSI C work nowadays. Certainly 
here in Europe, the highest demand is for Java, followed by various 
Microsoft and database technologies, then C++ and way way down the 
list is old fashioned C.

> I do the same with programming approaches, e.g., structured
> programming vs. object-based vs. full object-oriented vs. this thing
> you're cooking up. I'd like to learn more about your thing so I can
> use it someday, but ultimately I'll need to find a business and
> technical spot where it's a good fit.

Unfortunately, AFAICS there aren't the tools for my philosophy out 
there because work in my way of thinking stopped a few years ago. 
Indeed, Unix up until the kernel point is good (pretty fixed a good 
few years ago), set (ie; data) based languages seem to have mostly 
died after 1994-1996, good old RISC-OS was dead post-1996 - well, all 
those things I could place hand on heart and say "wonderful, this is 
good", they aren't being taken in their logical directions anymore - 
you could say, their lines of thinking have been mostly abandoned.

See http://www.paulgraham.com/noop.html - I will say I can see plenty 
of point to OO and I have used it successfully many times. However, I 
still don't think it's the best approach for most problems in the 
form it is currently used - and I *do* agree about the popular OO 
mania currently in effect (and we've discussed the causes of that 
mania).

> >>>Surely you would 
> >>>agree that if you base your design on quantities of data and the
> >>>overheads of the media in which they reside, you naturally and
> >>>intuitively produce a much more efficient design?
> 
> I honestly don't know enough about what you're doing to agree or
> disagree.
> 
> ***HOWEVER*** even if I agreed fully with that statement, I still
> don't think it has much to do with whether your stuff should be used
> on a given project. I honestly believe language- and
> technique-selection should be based on things like who the programmers
> are, what they know, whether the universities are churning out more
> programmers for us, whether we're going to be able to replace the ones
> we have if they quit, etc., in addition to the technical aspects you
> mentioned above. Just because "X" is a better mousetrap than "Y"
> doesn't mean we should use "X". We should use "X" if and only if "X"
> will reduce the overall company time, cost, and risk. And that
> includes the time and cost for retraining, the risk of losing the
> people we have who don't make the transition, and the risk of being
> held hostage by our programmers (e.g., if we choose a technology where
> there are only a few competent programmers, we might end up having to
> pay through the nose just to keep the ones we have).

As you said before, many a better technology have fallen by the 
wayside throughout the years. I think we've covered a good proportion 
of the reasons why in this dialogue - the big question now is can one 
man change the world? :)

> >Ok, I think we're escaping the fundamental core of this thread.
> >Basically, what I am saying, is that across all the software projects
> > in all the world, people are mostly applying an OO-based solution as
> > a primary leader. I feel this produces worse quality software
> >because of the problems with lack of intuition
> 
> Which may be true. But the fact that "people are mostly applying an
> OO-based solution as a primary leader" means there are a lot of
> programmers out there, and there will be a lot of tool vendors and
> compiler vendors to choose from, and we'll be able to get
> off-the-shelf libraries, and we'll be able to get consultants to help
> out in a pinch, and we'll have the choice whether to rent or buy our
> programmers, and, and, and.

However, there are always grass-roots movements. Maybe we believe in 
those more here in Europe than the US. There are many contributory 
factors, and indeed even if it fails it doesn't matter if you 
significantly improved yourself and the lives of others along the way 
eg; Marx's teachings haven't had much success, but can you imagine 
the world without them?

> Actually I really like your spunk and determination, and I really
> shouldn't try to throw a wet towel on your fire. You *need* your fire
> since otherwise you won't be able to finish what you've started.

Not at all. I greatly desire intelligent criticism, otherwise I am 
doomed to waste a great deal of my time on fool's errands.

> Tell you what: let's not talk about being language neutral any more,
> since it will not help you. Instead, please tell me about your
> paradigm. Show me some examples.

TWindow main;
TDataText clock(main, "dc:time");
TDataImage image(main, "/Storage/C/foo.jpg;5");
main.show();

That sticks a clock with the current time plus a view of version five 
of c:\foo.jpg in a window and shows it. If you made TDataText clock a 
TDataVector clock you'd get a graphical clock instead (because the 
type of data is determined by compatibility of provided interfaces). 
Literally, that's all the code you need.

TDataStream 
c1=TDataStream::connect("/Storage/C/myfile.txt","dc:grep");
c1.dest().setMetadata("Parameters", "grep pars - I forget");
TWindow main;
TDataText input(main, c1.dest())
main.show();

That greps the file c:\myfile.txt for whatever and stuffs the results 
in a window. No processing occurs whatsoever until the main.show(). 
Basically, if you use your imagination, I'm sure you can see how 
everything else fits together.

My only major problem is that of active non-simple types eg; a HTML 
file - because it requires as a basic part of its functioning direct 
interaction with the user. Now of course, a traditional custom 
written component can handle this, but that doesn't encourage code 
reuse so I'll need to come up with a way of making QWidget work 
across process boundaries. This will not be an easy technical 
challenge :( (I'm thinking I'll wait till version 2).

Obviously, what takes three lines above can be put into one much less 
obtruse line in my own custom language. I think you can clearly see 
functional tendencies already appearing in the class design, but my 
own language would remove all the grunge code as well.

Cheers,
Niall




From: "Marshall Cline" <[email protected]>
To: "'Niall Douglas'" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Sun, 4 Aug 2002 14:54:38 -0500

Hi Niall,

I am packing for a business trip, so don't have time to go through a
long reply. (I can hear your sigh of relief already!) I had time to
read it completely, but not enough time to reply.

A couple of quick comments:

======================================================================

1. Re a lack of tabula rasa, agreed that we are all influenced by our
particular experiences. I try to keep an open mind wrt OO vs.
procedural vs. functional vs. logic-oriented vs. constraint-oriented,
but certainly there are limits to how even-keeled any of us can actually
be.

======================================================================

2. Re what you said about embedded and handheld systems getting so
powerful that they don't need assembly makes a lot of sense. For
wireless devices, we may even get to the point where we don't constantly
worry about squeezing functionality into these tiny boxes, both because
the boxes won't be so tiny, and also because the functionality might end
up getting loaded on-demand (either that, or the functionality runs on
servers in the ether, and the device merely communicates with them).
The point is that the world is changing, and it's ultimately going to
dumb-down the kind of coding that happens on just about all computing
device everywhere. 

(One of the trends I've noticed, and you alluded to it earlier, is how
the skills required to be a decent programmer have changed over the
years. 20 years ago, the key was the ability to solve ill-formed
problems - to actually be able to figure things out. Today you need a
good memory, since the key is to know the packages that are available.
In other words, we've moved from a world where most of the code in a
system was written by our own programmers to one where our programmers
merely glue pre-written pieces together. I personally find that sad,
probably because I'm pretty good at the old way of doing things and I
don't have the patience to actually *study* the latest tools written up
in rags like BYTE magazine. But regardless of how it effects you or me,
there does seem to be a clear trend toward knowledge / information, and
a corresponding de-emphasis on insight / problem solving skills /
ability to think out of the box.)

======================================================================

3. A brief comment about this exchange:

Marshall:
>>However I think we both played a part in this communication problem.
>>For example, when I first explained the "require no more and promise
>>no less" idea in my previous email, you replied, "This is quite
>>ephemeral and subtle..." Although it is clearly subtle at times, I
>>see it as the opposite of ephemeral, and since, perhaps as a
>>back-handed compliment to you (e.g., "I *know* this guy is bright, so
>>if he thinks it is ephemeral, I must not have explained it very
>>well"), I re-explained it.
>
Niall:
>In which case, I must explain myself as well - why I said that in 
>that fashion wasn't just because of you, but also for the benefit of 
>the others following this conversation. I *do* however think it 
>subtle because, like you said before, it's not a programming error.

I don't know what you meant by "it" in the last sentence, but I'm
assuming (perhaps wrongly) that "it" refers back to the original example
that you said was ephemeral and subtle, namely improper inheritance. In
that case, I'm not sure why you said it's not a programming error. I
see a few possibilities:
A) Perhaps what you meant it's not a programming error that is caught by
the compiler -- that causes a diagnostic message from the compiler. If
that's what you meant, then of course, you're correct (and that goes
along with what you said about it being subtle).
B) Perhaps you meant it's not a programming error in the sense that it's
more of a design or conceptual error. I suppose that's also correct: it
is an error that primarily started at the design or conceptual level.
It clearly shows up in the code, and therefore is *also* a programming
error, but perhaps you meant to emphasize the design/conceptual error.
C) But if you meant to imply that proper inheritance is merely a "best
practice" that doesn't really effect the code's correctness (e.g., if it
effects only the code's maintainability, programmer productivity, or any
other non-functional goal), then I must disagree. Improper causes all
sorts of correctness problems in the code, and in that sense it is a
programming error.

======================================================================

4. WRT your functional style, you might want to look into using
generic-programming (in C++) instead of OO. From what I've seen of your
ideas, generic programming via C++ might let you achieve some (most?) of
the benefits of having your own language without the associated costs.
It's really a different way of programming, but some of the things you
described at the very end (in your two examples) seem to match pretty
closely with the generic programming idea. I'd suggest starting with
the Lambda library at www.boost.org (there's a link to the Lambda
library on boost's opening page).

======================================================================

I wish you the best.

Marshall




From: Niall Douglas <[email protected]>
To: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ
Date: Mon, 5 Aug 2002 19:55:23 +0200

On 4 Aug 2002 at 14:54, Marshall Cline wrote:

> I am packing for a business trip, so don't have time to go through a
> long reply. (I can hear your sigh of relief already!) I had time to
> read it completely, but not enough time to reply.

Nah it's cool. I would have had to have ended it anyway end of this 
week as I go on holiday.

> (One of the trends I've noticed, and you alluded to it earlier, is how
> the skills required to be a decent programmer have changed over the
> years. 20 years ago, the key was the ability to solve ill-formed
> problems - to actually be able to figure things out. Today you need a
> good memory, since the key is to know the packages that are available.
> In other words, we've moved from a world where most of the code in a
> system was written by our own programmers to one where our programmers
> merely glue pre-written pieces together. 

Absolutely.

> I personally find that sad,
> probably because I'm pretty good at the old way of doing things and I
> don't have the patience to actually *study* the latest tools written
> up in rags like BYTE magazine. But regardless of how it effects you
> or me, there does seem to be a clear trend toward knowledge /
> information, and a corresponding de-emphasis on insight / problem
> solving skills / ability to think out of the box.)

Yeah the knowledge vs. skill balance is definitely tilting.

> 4. WRT your functional style, you might want to look into using
> generic-programming (in C++) instead of OO. From what I've seen of
> your ideas, generic programming via C++ might let you achieve some
> (most?) of the benefits of having your own language without the
> associated costs. It's really a different way of programming, but some
> of the things you described at the very end (in your two examples)
> seem to match pretty closely with the generic programming idea. I'd
> suggest starting with the Lambda library at www.boost.org (there's a
> link to the Lambda library on boost's opening page).

Ah good old lambda algebra! That toolkit you mentioned is an amazing 
example of what can be done in C++, and even better an example of how 
good compilers can optimise (ie; the GCC 3.0 benchmarks).

> I wish you the best.

Yeah you too. If you ever need an assembler programmer outside the 
US, give me a call. I'd take a pay cut to work with competent people 
especially if the work is challenging or interesting.

Two things:
1. For your FAQ, am I right in thinking it's a good idea to either 
make base class copy constructors protected or virtual? The first 
stops copy slicing by giving a compile error if you make a base class 
copy. The second forces use of the derived class' copy constructor.

2. Can you suggest the following next time C++ wants new features:

class String
{
Mutex mutex;
public:
pre String() { mutex.lock(); }
post String() { mutex.unlock(); }
...
};

The idea being that the compiler inserts pre and post code before and 
after every access to String. The use is for multithreading but could 
be useful for other kludges too. You can of course make pre and post 
virtual.

Cheers,
Niall




To: "Niall Douglas" <[email protected]>
Date: Tue, 06 Aug 2002 11:35:16 -0500
From: "Marshall Cline" <[email protected]>
Subject: RE: Comments on your C++ FAQ

Hi Niall,

Re assignment, yes that's the right idea. Most of the 
time they can be protected, since most of the time the 
derived classes aren't assignment compatible. E.g., 
if the base class is Fruit, it doesn't make sense to 
assign an Apple with an Orange, so it should be 
protected. If the base class is Set, with derived 
classes SetUsingHashTable and SetUsingBinaryTree, 
etc., then it makes sense to assign them so it should 
probably be virtual, and perhaps pure virtual (since 
it needs probably to be overridden in the derived 
class's anyway; although you *might* be able to 
implement it in the base class by calling virtual 
functions in both 'this' (to insert elements) and in 
the other Set object (to access elements)).

Re your suggestion, good idea - thanks.

Marshall

fin

blog comments powered by Disqus