Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Generic Programming; Eiffel vs. C++ and the max() Test (long)

29 views
Skip to first unread message

Greg

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
A post on Generic Programmng (GP) cited interviews with Dr. Alexandr
Stepanov, one of the proponents of the C++ STL and GP.

http://www.sgi.com/Technology/STL/drdobbs-interview.html
http://www.stlport.org/resources/StepanovUSA.html

I've read through the interviews, and in them are three tests that Dr.
Stepanov might apply to qualify a language for GP. I'd like to discuss
one of these tests, how Dr. Stepanov implemented it in one of the
interviews, and how the same test can be implemented in Eiffel. (In
another thread, I debated with a couple of people over the merits of
C++ vs Eiffel's generic programming facilities.)

First, here's a version of Dr. Stepanov's implementation.

template <class T>
inline T& max(T& x, T& y) {
return x < y ? y : x;
}

template <class T>
inline const T& max(const T& x, const T& y) {
return x < y ? y : x;
}

For details on his implementation, I refer you to the STLPort.org
article referenced above. Dr. Stepanov accounts for the fact that for
some classes, operator< () might modify the object, and for other
classes, max should guarantee immutability.

Now here's an implementation of max() in Eiffel:

max (x: COMPARABLE; y : like x) : like x is
require y_exists: y /= Void
do
Result := x
if x < y then
Result := y
end
end

The two implementations are not too different in appearance, but the
devil is in the details and in this case, the details are in the
implementation.

The C++ implementation requires that the parameter class T has "T&
operator<()" defined for it. However, there is no enforcement that
operator<() perform a comparison.

Worse yet, max() has very limited utility. It will work in the case of
classes that have properly defined operator<. It will not work in the
face of simple polymorphism as in this example:

// Assume Dog and Giraffe are descendents of Animal, and that
// it should be an error to compare dogs to giraffes.
//
Animal& a1 = Dog;
Animal& a2 = Giraffe;
Animal& a3 = max (a1, a2); // no error detected.

And an even more simple test case:

cout << max (100, 30) << endl;

generates an error with my C++ compiler (VC++ 6.0, for the record.) The
error? The compiler can't determine which of the two versions of max
should apply.

Is it possible to correct these deficiencies in the C++ implemenation?
I believe so, but it would require access to all of the source for all
of the classes involved. And it would require substantially more code
than in the original version.

I'd like to challenge you to present an implementation that produces a
correct answer in the face of polymorphism, const and non-const
variables, and max (100, 3). (Keep in mind that I'm looking at cases
where "the correct answer" is compile-time or run-time error detection
when the code is incorrect.)

class Animal ( ... };
class Dog : public Animal { ... };
class Giraffe : public Animal { ... };
...
Dog d1, d2;
Giraffe g1, g2;
Animal &a1 = d1, // a1 is a dog
&a2 = d2, // as is a2
&a3 = g1; // a3 is a giraffe

max (d1, d2); // OK
max (d1, g1); // compilation error
max (a1, a2); // OK, same type
max (a1, a3); // run-time error
max (100, 3);

Further, I'd like to see a solution that works even if the
implementation of max() changes to use operator> instead of operator<.

On to the Eiffel example. Note is that in the declaration of max(), the
first parameter is declared to be a COMPARABLE. This class defines an
interface and a contract for the common comparison operators < > /=
(not-equal) = (is equal) <= and >=. Only the operator "<" is left
unimplemented.

Inheriting from COMPARABLE forces the implementor to write a "<"
function that conforms to the contract. The contract states in part
that asymmetry must hold for "<". In other words,
a < b iff b >= a
and
a < b equals not b < a

An example of COMPARABLE's interface and contract can be found at:
http://smalleiffel.loria.fr/libraries/classes/comparable.html

One could inherit from COMPARABLE and usurp "<" to do something totally
unrelated to comparison, like write to a file, but the contract makes
this pretty difficult. That is, the function would have to know that
a < b
and
b < a
must give different answers.

The max() function guarantees that both parameters are compatible and
can be compared legally. This is true even in the face of polymorphism,
where the C++ implementation fails. In other words, it works for all
the cases I've outlined above ( even max (100, 3) :-)

Does the Eiffel implementation have shortcomings that the C++
implementation does not? For one thing, Eiffel doesn't have the same
concept of immutibility (ie "const-ness") that C++ does. So for better
or worse, there is no way to guarantee that max() won't modify it's
parameters. On the other hand, one could supply contracts for the
classes being compared, forcing them to check that they have not been
modified after the comparison.

Other than that, I've not yet found a way to break max() so that either
a coding error is undetected or a legal comparison can't be performed.


In conclusion:
a) Eiffel's implementation of constrained genericity and covariance
provides superior generic programming facilities over C++ templates.

b) In this case at least, the implementation of max() does more with
far less source code than does any C++ implementation (ironic for a
language that is often criticized for being verbose, being compared to
one often praised for its brevity).

c) As is common with C++, language features provide primitive
mechanisms for implementing a facility, in this case templates for
implementing generic programming. However, because the mechanisms are
primitive, it is difficult or impossible to come up with robust general
solutions. Further, the author of these solutions have to go an
enormous amount of work to get the details right.

--
http://homestead.deja.com/user.gmc333/index.html


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]


Alexei Lebedev

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
Your challenge is incorrect, because if both Dog and Giraffe are publicly
derived from Animal,
and Animal has a comparison operator, then both Dog and Giraffe implement
the Animal interface. So it is valid to compare them as Animals.

Alexei Lebedev

Ken Bloom

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
"Greg" <gmc...@my-deja.com> wrote in message
news:8tvk87$96t$1...@nnrp1.deja.com...

Query: where do you get that max(a1,a3) gives you a run-time error?

Either an op< is defined for Animal, in which case it runs successfully, or
it is not (op< may be defined individually for Dog and for Giraffe either
way), resulting in a compilation error.


> Further, I'd like to see a solution that works even if the
> implementation of max() changes to use operator> instead of operator<.

Most of the C++ library is implemented as source code for this reason. C++
is so powerful that many things that previously had to be built into the
language can be implemented as classes. C++ makes no guarantee that the
semantics are correct for your user defined types, but if you guarantee this
by for example defining all 6 comparison operators whenever you define one
of them, then you should have no issue here.


> On to the Eiffel example. Note is that in the declaration of max(), the
> first parameter is declared to be a COMPARABLE. This class defines an
> interface and a contract for the common comparison operators < > /=
> (not-equal) = (is equal) <= and >=. Only the operator "<" is left
> unimplemented.

You can do that in C++, but it isn't required like in eiffel. boost's
operators.hpp does a really good job of ensuring proper semantics of all the
operators.

> Inheriting from COMPARABLE forces the implementor to write a "<"
> function that conforms to the contract. The contract states in part
> that asymmetry must hold for "<". In other words,
> a < b iff b >= a
> and
> a < b equals not b < a
>
> An example of COMPARABLE's interface and contract can be found at:
> http://smalleiffel.loria.fr/libraries/classes/comparable.html
>
> One could inherit from COMPARABLE and usurp "<" to do something totally
> unrelated to comparison, like write to a file, but the contract makes
> this pretty difficult. That is, the function would have to know that
> a < b
> and
> b < a
> must give different answers.
>
> The max() function guarantees that both parameters are compatible and
> can be compared legally. This is true even in the face of polymorphism,

> where the C++ implementation fails...

THE C++ EXAMPLE DOES NOT FAIL IN THE CASE OF POLYMORPHISM.

> ... In other words, it works for all


> the cases I've outlined above ( even max (100, 3) :-)
>
> Does the Eiffel implementation have shortcomings that the C++
> implementation does not? For one thing, Eiffel doesn't have the same
> concept of immutibility (ie "const-ness") that C++ does. So for better
> or worse, there is no way to guarantee that max() won't modify it's
> parameters. On the other hand, one could supply contracts for the
> classes being compared, forcing them to check that they have not been
> modified after the comparison.
>
> Other than that, I've not yet found a way to break max() so that either
> a coding error is undetected or a legal comparison can't be performed.
>
>


--
Ken Bloom

-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCS/M/AT/U d- s++:--- a--- C++ UL P++ L+ E----
W+++ N++ ?o ?K w++ !O M++>$ V- PS PE Y-- PGP- t+
5 X++ R--- tv-- b++ DI+ D-- G e- !h r--@ y?
------END GEEK CODE BLOCK------

Peter Bertok

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to

> template <class T>
> inline T& max(T& x, T& y) {
> return x < y ? y : x;
> }
>
> template <class T>
> inline const T& max(const T& x, const T& y) {
> return x < y ? y : x;
> }

Not only will those two cause ambiguity errors, there is also this
situation:

const int& foo = max( 1 + 2, 2 + 3 );

The error is usually something like "Cannot return the address of a
temporary."

In other words, there should also be a THIRD max:

template <class T>
inline const T max(const T& x, const T& y) {
return x < y ? y : x; // Returns a copy of x or y, not a
reference.

Ross Smith

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
Greg wrote:
>
> [ C++ implementation of max() ]

>
> template <class T>
> inline T& max(T& x, T& y) {
> return x < y ? y : x;
> }
>
> template <class T>
> inline const T& max(const T& x, const T& y) {
> return x < y ? y : x;
> }
>
> [ Eiffel implementation ]

>
> max (x: COMPARABLE; y : like x) : like x is
> require y_exists: y /= Void
> do
> Result := x
> if x < y then
> Result := y
> end
> end

...

> Worse yet, max() has very limited utility. It will work in the case of
> classes that have properly defined operator<. It will not work in the
> face of simple polymorphism as in this example:
>
> // Assume Dog and Giraffe are descendents of Animal, and that
> // it should be an error to compare dogs to giraffes.
> //
> Animal& a1 = Dog;
> Animal& a2 = Giraffe;
> Animal& a3 = max (a1, a2); // no error detected.

This is the fault of whoever designed the classes. If Animal<Animal is
legal but Dog<Giraffe isn't, the Liskov substitution principle has been
broken. I don't think you can blame the language for a design cockup as
egregious as that.

Polymorphism and comparison operators generally don't play well together
regardless of your choice of language. Where it's practical to provide
comparisons for a polymorphic class hierarchy, it's usually because the
comparison is dependent only on the common properties available through
the base class, so a max() operation that devolves to a base class
comparison is the Right Thing. When that _isn't_ the right thing, the
general solution requires multiple dispatch, which I don't believe is
supported by Eiffel either. (You can get a pretty good approximation in
C++ with a bit of extra work; I don't know enough about Eiffel to hazard
any guess as to whether it's easier or harder there.)

> And an even more simple test case:
>
> cout << max (100, 30) << endl;
>
> generates an error with my C++ compiler (VC++ 6.0, for the record.) The
> error? The compiler can't determine which of the two versions of max
> should apply.

Compiler bug.

Do you really think it's reasonable to criticise a language without even
learning enough about it to tell the difference between a language
restriction and a compiler bug?

> Is it possible to correct these deficiencies in the C++ implemenation?
> I believe so, but it would require access to all of the source for all
> of the classes involved.

Which is also true of the Eiffel solution you propose, which imposes the
drastic rule that all classes that implement operator< have a common
base class.

I'm not intimately familiar with Eiffel so I waon't try to criticise
your Eiffel implementation, beyond pointing out that it's asymmetric
(the classic problem with operators in "silver bullet" languages that
try to make everything OO), and that your description of the properties
that should be required of the less-than operator is very confused (the
definition on the web page you point to gets it right, though, so I
assume the mistakes are yours rather than Eiffel's).

If you want a real criticism of C++ in this area, mine would be based on
this example:

long foo = 1;
long bar = max(foo, 2);

This gives you an ambiguity error (unadorned numbers are ints unless
they're out of range, and the compiler can't pick a best-match
instantiation of the template since int->long and long->int are equally
good conversions).

I think the choice not to fix C++'s arithmetic type system (simplify and
disambiguate the conversion rules, and do away with the primitive/UDT
distinction) was a big mistake.

--
Ross Smith <ros...@ihug.co.nz> The Internet Group, Auckland, New Zealand
========================================================================
"C++ is to programming as sex is to reproduction. Better ways might
technically exist but they're not nearly as much fun." -- Nikolai Irgens

Dietmar Kuehl

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
Hi,
Greg (gmc...@my-deja.com) wrote:
: The C++ implementation requires that the parameter class T has "T&

: operator<()" defined for it. However, there is no enforcement that
: operator<() perform a comparison.

Which is a reasonable assumption: Somehow you have to tell the 'max()'
function according to what criterium it should compare the values.
If really necessary, it is pretty simple to remove this restriction and
rather require a comparator object

: Worse yet, max() has very limited utility. It will work in the case of


: classes that have properly defined operator<. It will not work in the
: face of simple polymorphism as in this example:

: // Assume Dog and Giraffe are descendents of Animal, and that
: // it should be an error to compare dogs to giraffes.
: //
: Animal& a1 = Dog;
: Animal& a2 = Giraffe;
: Animal& a3 = max (a1, a2); // no error detected.

This is a problem with your definition: If you can compare animals,
it is a violation of the Liskov Substitution Principle if you cannot
compare Dogs and Giraffes since, after all, these are animals. If
you cannot compare animals, ie. there is no definition of operator<()
on animals, the above 'max()' function is rejected at compile time.
This is *exactly* the correct semantics, even in the presence of
dynamic polymorphism!

: And an even more simple test case:

: cout << max (100, 30) << endl;

: generates an error with my C++ compiler (VC++ 6.0, for the record.) The
: error? The compiler can't determine which of the two versions of max
: should apply.

This is a bug in the compiler: VC++ is pretty bad on partial template
function ordering. A conforming compiler can select the correct version
(the one taking the 'T const&' as argument).

: Is it possible to correct these deficiencies in the C++ implemenation?

Which deficiencies? There is non! The first "deficiency" is a flaw in your
understanding of object orientation (ie. a deliberate violation of the
LSP), the second "deficiency" is compiler bug.

: I believe so, but it would require access to all of the source for all


: of the classes involved. And it would require substantially more code
: than in the original version.

Your believe is wrong.

: I'd like to challenge you to present an implementation that produces a


: correct answer in the face of polymorphism, const and non-const
: variables, and max (100, 3). (Keep in mind that I'm looking at cases
: where "the correct answer" is compile-time or run-time error detection
: when the code is incorrect.)

: class Animal ( ... };
: class Dog : public Animal { ... };
: class Giraffe : public Animal { ... };
: ...
: Dog d1, d2;
: Giraffe g1, g2;
: Animal &a1 = d1, // a1 is a dog
: &a2 = d2, // as is a2
: &a3 = g1; // a3 is a giraffe

: max (d1, d2); // OK
: max (d1, g1); // compilation error
: max (a1, a2); // OK, same type
: max (a1, a3); // run-time error

If this is a considered to be a run-time error (which it can be made if
you really want to) your design is broken: If you can compare
'Animal's, you *have to* be able to compare *all* kinds of animals,
even if they are of different type! If you cannot compare all kinds of
'Animal's, don't pretend to be able to do so and move the functions
down to the correct level in your hierarchy.

: max (100, 3);

: Further, I'd like to see a solution that works even if the
: implementation of max() changes to use operator> instead of operator<.

That's just boring: Add another template argument for a comparator
object.

: The max() function guarantees that both parameters are compatible and


: can be compared legally. This is true even in the face of polymorphism,
: where the C++ implementation fails. In other words, it works for all
: the cases I've outlined above ( even max (100, 3) :-)

Using a simple base class 'comparable' the exact same thing can be
implemented in C++, too. So where is your point?

: Does the Eiffel implementation have shortcomings that the C++
: implementation does not?

Since I don't know Eiffel, this is hard to tell. The crucial part of
the C++ 'max()' function, is that the type returned is the type of the
arguments, not some other type, eg. a base class. It is actually
equally important, that the arguments are not required to be derived
from some specific class/interface/however you call it, like eg. the
"COMPARABLE": They are only required to conform to a certain abstract
concept, for 'max()' is the concept of being "less than comparable".
The exact meaning of what "less than comparable" means is, BTW, well
defined by the C++ standard (20.1.2 - Less than comparison
[lib.lessthancomparable] which basically says that 'operator<()' has to
be a strict weak ordering).

: Other than that, I've not yet found a way to break max() so that either


: a coding error is undetected or a legal comparison can't be performed.

In C++ the following works:

struct Dog {
void barf() const { std::cout << "barf!\n"; }
bool operator< (Dog const&) const { return true; }
};

max(Dog(), Dog()).barf();

For generic programming it is crucial that this works, in particular it
is necessary that the return type of 'max()' is of type 'Dog'. If it
does work for Eiffel, Eiffel might be usable to do generic
programming. However, this is not the only restriction. It is also
necessary that certain operation return types depending on the exact
type of the function arguments, not just some type know to a base
class. For example, the function accessing an element identified by an
iterator (in C++ 'operator*()') has to return a type depending on the
underlying sequence: If the iterator is eg. moving over a sequence of
'int's, the result of accessing an element has to be a 'int', too. If
the underlying sequence has some other type, the function accessing the
element has to return an object (or reference to such an object) of the
corresponding type.

: In conclusion:


: a) Eiffel's implementation of constrained genericity and covariance
: provides superior generic programming facilities over C++ templates.

Take your "Eiffel is brilliant, everything else is crap"-glasses off
and make the evaluation again... Also, the implementation of the
'max()' function is a crucial part but not sufficient. There are other
aspects necessary for true generic programming as well.

: b) In this case at least, the implementation of max() does more with


: far less source code than does any C++ implementation (ironic for a
: language that is often criticized for being verbose, being compared to
: one often praised for its brevity).

You change the underlying requirements (C++: provide a function called
'operator<()' which defines a strict weak ordering; Eiffel: derive from
"COMPARABLE" and define an appropriate function defining an order).
Given the *much* stronger requirements of the Eiffel implementation, it
is no surprise that the corresponding code can be made simpler! With
the same requirements the same is possible with C++ - the code would
just go into the "COMPARABLE" base class. ... and, BTW, the *same*
'max()' function as the one above can be used. Here is a corresponding
'comparable' base class:

struct comparable
{
virtual ~comparable() {}
bool operator< (comparable const& comp) const
{
if (typeid(*this) != typeid(comp))
throw "argument types don't match!";
return compare(comp);
}
private:
virtual bool compare(comparable const& comp) const = 0;
};

That is, don't compare apples to oranges. Neither in real live nor
when comparing programming languages.

: c) As is common with C++, language features provide primitive


: mechanisms for implementing a facility, in this case templates for
: implementing generic programming. However, because the mechanisms are
: primitive, it is difficult or impossible to come up with robust general
: solutions. Further, the author of these solutions have to go an
: enormous amount of work to get the details right.

This is a religous approach to comparing computer languages. Since it
makes not sense to discuss on such a level I'm not going to comment on
this except stating that there are other religions out there which have
a different view on this.
--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

Dietmar Kuehl

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
Hi,
Peter Bertok (pe...@bertok.com) wrote:
: > template <class T>

: > inline T& max(T& x, T& y) {
: > return x < y ? y : x;
: > }
: >
: > template <class T>
: > inline const T& max(const T& x, const T& y) {
: > return x < y ? y : x;
: > }

: Not only will those two cause ambiguity errors,

Can you please spell out any situation where these two definitions might
provide an ambiguity error? Just one! (hint: these functions are under
no circumstances ambiguous). ... and don't try such a lame approach as
"my [V]C++ compiler tells me that these two function are ambiguous"!
Yes, there are broken compilers out there.

: there is also this situation:

: const int& foo = max( 1 + 2, 2 + 3 );

: The error is usually something like "Cannot return the address of a
: temporary."

This is an error stemming from a broken compiler! The arguments passed
to the 'max()' functions are bound to a 'int const&' which can be
returned from the 'max()' function without problem. The only thing
which might cause a problem is the life time of the object bound to
'foo': It is possible that it might not be accessible in the next
statement (I'm not sure about the details but I wouldn't personally try
to play tricks on this one). As long as you use the result only in the
same statement this is no problem, however: Temporaries are destructed
at the end of the enclosing full expression.

: In other words, there should also be a THIRD max:

: template <class T>
: inline const T max(const T& x, const T& y) {
: return x < y ? y : x; // Returns a copy of x or y, not a
: reference.
: }

No, you don't need a third a version. The two versions given above are
just fine.


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Frank Muzzulini

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
"Peter Bertok" <pe...@bertok.com> writes:
> > template <class T>
> > inline T& max(T& x, T& y) {
> > return x < y ? y : x;
> > }
> >
> > template <class T>
> > inline const T& max(const T& x, const T& y) {
> > return x < y ? y : x;
> > }
>
> Not only will those two cause ambiguity errors, there is also this

> situation:
>
> const int& foo = max( 1 + 2, 2 + 3 );
>
> The error is usually something like "Cannot return the address of a
> temporary."
>
> In other words, there should also be a THIRD max:
>
> template <class T>
> inline const T max(const T& x, const T& y) {

> return x < y ? y : x; // Returns a copy of x or y, not a
> reference.
> }

Aside from being unnecessary (see Dietmar), the third version now
really gives an ambiguity, since it differs from the second only in
its return value. C++ does never look at the return type, to choose
between two overlooded versions of a function and even if it would do,
a 'const T&' can be used, where ever a 'const T' can be used.

Additionally if max returns an object instead of a reference, it is a
temporary one, so


const int& foo = max( 1 + 2, 2 + 3 );

will be nonsense, you can not savely use this reference in any
subsequent statement. On the other hand, using the second version, the
reference would still point to a compile-time constant holding the
value 5 (=2+3).

Muzz
--
___ Frank Muzzulini
<*,*> ... until the colour of a man skin is of no more
[`-'] significance than the colour of his eyes ... Haile Selassie
-"-"-

Greg

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
In article <t08squp...@corp.supernews.com>,

"Alexei Lebedev" <ale...@metastream.com> wrote:
> Your challenge is incorrect, because if both Dog and Giraffe are
publicly
> derived from Animal,
> and Animal has a comparison operator, then both Dog and Giraffe
implement
> the Animal interface. So it is valid to compare them as Animals.

Alexei, if I'm in error in my design, then the error is originally
Stepanov's. I refer you to the Dr Dobbs interview and Stepanov's
discussion of mating animals.

I said nothing about Animal implementing a comparison operator. It may
only define an abstract interface to it, or may not even do that.

The design is not bad, it's artificial:-) Certainly in real-life
applications there are operations that one would like to perform on two
objects of the same type, and performing them on objects of different
types should be in error.

My point, in part, is how difficult it is in C++ to implement a feature
as simple as max() and have it work correctly (including error
detection) across many circumstances.

Greg

Greg

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
In article <8u262o$a8l$1...@news.BelWue.DE>,

dietma...@yahoo.com wrote:
> Hi,
> Peter Bertok (pe...@bertok.com) wrote:
>: > template <class T>

>: > inline T& max(T& x, T& y) {
>: > return x < y ? y : x;
>: > }
>: >
>: > template <class T>
>: > inline const T& max(const T& x, const T& y) {
>: > return x < y ? y : x;
>: > }
>
>: Not only will those two cause ambiguity errors,
>
> Can you please spell out any situation where these two definitions
might
> provide an ambiguity error? Just one! (hint: these functions are under
> no circumstances ambiguous). ... and don't try such a lame approach as
> "my [V]C++ compiler tells me that these two function are ambiguous"!
> Yes, there are broken compilers out there.
>
[...]

So you have tested this code on another compiler and it works? If so,
please show me the test program you used and specify the compiler(s).
I'd like to see if I can get access to it for more testing.

If I can confirm this is a compiler bug, I'll retract that criticism.

Greg

Greg

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
In article <8u22er$chi$1...@slb7.atl.mindspring.net>,

"Ken Bloom" <ken...@bigfoot.com> wrote:
> "Greg" <gmc...@my-deja.com> wrote in message
[...]

> >
> > // Assume Dog and Giraffe are descendents of Animal, and that
> > // it should be an error to compare dogs to giraffes.
> > //
[...]

> >
> > class Animal ( ... };
> > class Dog : public Animal { ... };
> > class Giraffe : public Animal { ... };
> > ...
> > Dog d1, d2;
> > Giraffe g1, g2;
> > Animal &a1 = d1, // a1 is a dog
> > &a2 = d2, // as is a2
> > &a3 = g1; // a3 is a giraffe
> >
> > max (d1, d2); // OK
> > max (d1, g1); // compilation error
> > max (a1, a2); // OK, same type
> > max (a1, a3); // run-time error
> > max (100, 3);
>
> Query: where do you get that max(a1,a3) gives you a run-time error?
>
> Either an op< is defined for Animal, in which case it runs
successfully, or
> it is not (op< may be defined individually for Dog and for Giraffe
either
> way), resulting in a compilation error.
>

The problem with a long post is that it gets tough to track all the
details. I have a requirement (admittedly artificial) that comparing
different species of animals should be an error. My point is that the
definition of max() from Stepanov's interview can't detect the runtime
error.

> > Further, I'd like to see a solution that works even if the
> > implementation of max() changes to use operator> instead of
operator<.
>
> Most of the C++ library is implemented as source code for this reason.
C++
> is so powerful that many things that previously had to be built into
the
> language can be implemented as classes. C++ makes no guarantee that
the
> semantics are correct for your user defined types, but if you
guarantee this
> by for example defining all 6 comparison operators whenever you define
one
> of them, then you should have no issue here.
>

Hmmm... reading this with my devil's advocate hat on, I could interpret
what you say as "C++ is so powerful, we can no longer ship binary
libraries, there is no enforcement of semantics, and if you want
comparison operators, you get to write them (all of them) from scratch
everywhere". None of this really sounds like "power" to me :-)

Seriously, I see many of the things you're touting as negatives. Why
can't I ship usable binary libraries? Why can't some level of semantics
be guaranteed for me? Is it really necessary to do more work instead of
less?


[..]


>
> THE C++ EXAMPLE DOES NOT FAIL IN THE CASE OF POLYMORPHISM.

Unfortunately, in my opinion the max() function I presented does fail,
in that it doesn't detect the runtime error. If you have a compiler
where the error is detected, or an implementation of max() which detects
it, I'd like to see it.

Greg

Greg

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
In article <3A049D0A...@ihug.co.nz>,
Ross Smith <ros...@ihug.co.nz> wrote:
> Greg wrote:
[...]

> > // Assume Dog and Giraffe are descendents of Animal, and that
> > // it should be an error to compare dogs to giraffes.
> > //
> > Animal& a1 = Dog;
> > Animal& a2 = Giraffe;
> > Animal& a3 = max (a1, a2); // no error detected.
>
> This is the fault of whoever designed the classes. If Animal<Animal is
> legal but Dog<Giraffe isn't, the Liskov substitution principle has
been
> broken. I don't think you can blame the language for a design cockup
as
> egregious as that.

The design isn't bad, it's artificial :-)

This is just one example of a general concept. Certainly in the real
world there can be operations that can only be applied to objects of the
same type, and mixing types is an error. I'm simply saying this is a
circumstance where the C++ version doesn't detect this error and the
Eiffel version does.

Take a look at Stepanov's article, especially the Dr Dobbs article and
the discussion on Animal there.


>
> Polymorphism and comparison operators generally don't play well
together
> regardless of your choice of language. Where it's practical to provide
> comparisons for a polymorphic class hierarchy, it's usually because
the
> comparison is dependent only on the common properties available
through
> the base class, so a max() operation that devolves to a base class
> comparison is the Right Thing. When that _isn't_ the right thing, the
> general solution requires multiple dispatch, which I don't believe is
> supported by Eiffel either. (You can get a pretty good approximation
in
> C++ with a bit of extra work; I don't know enough about Eiffel to
hazard
> any guess as to whether it's easier or harder there.)
>

In C++, comparison operators are just another function. I could
extrapolate your comment to mean "in C++, polymorphism and functionality
don't mix well."

I've had no problem getting this to work in Eiffel. I think it can be
made to work in C++, but instead of being automatic, it's a lot of
effort.

Devolving the functionality to the base class could definitely be the
Wrong Thing in many instances.

> > And an even more simple test case:
> >
> > cout << max (100, 30) << endl;
> >
> > generates an error with my C++ compiler (VC++ 6.0, for the record.)
The
> > error? The compiler can't determine which of the two versions of max
> > should apply.
>

> Compiler bug.
>
> Do you really think it's reasonable to criticise a language without
even
> learning enough about it to tell the difference between a language
> restriction and a compiler bug?
>

Show me which compilers this does work on and I'll retract this
criticism. I'd like to see the test program and its output.

> > Is it possible to correct these deficiencies in the C++
implemenation?
> > I believe so, but it would require access to all of the source for
all
> > of the classes involved.
>

> Which is also true of the Eiffel solution you propose, which imposes
the
> drastic rule that all classes that implement operator< have a common
> base class.

The "drastic rule" is part of the original requirements. As I said, this
is an artificial example of a general principle.

>
> I'm not intimately familiar with Eiffel so I waon't try to criticise
> your Eiffel implementation, beyond pointing out that it's asymmetric
> (the classic problem with operators in "silver bullet" languages that
> try to make everything OO), and that your description of the
properties
> that should be required of the less-than operator is very confused
(the
> definition on the web page you point to gets it right, though, so I
> assume the mistakes are yours rather than Eiffel's).

Thanks, if I have a chance, I'll try to make it less confused.

>
> If you want a real criticism of C++ in this area, mine would be based
on
> this example:
>
> long foo = 1;
> long bar = max(foo, 2);
>
> This gives you an ambiguity error (unadorned numbers are ints unless
> they're out of range, and the compiler can't pick a best-match
> instantiation of the template since int->long and long->int are
equally
> good conversions).
>
> I think the choice not to fix C++'s arithmetic type system (simplify
and
> disambiguate the conversion rules, and do away with the primitive/UDT
> distinction) was a big mistake.
>
> --
> Ross Smith <ros...@ihug.co.nz> The Internet Group, Auckland, New
Zealand
>
========================================================================
> "C++ is to programming as sex is to reproduction. Better ways might
> technically exist but they're not nearly as much fun." -- Nikolai
Irgens
>

Howard Hinnant

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
In article <8u3pqs$8ia$1...@nnrp1.deja.com>, Greg <gmc...@my-deja.com> wrote:

| My point, in part, is how difficult it is in C++ to implement a feature
| as simple as max() and have it work correctly (including error
| detection) across many circumstances.

Setting aside for the moment questions of design, obtaining the behavior
you desire in C++ does not seem like that much work to me.

First off, pair your max template down to just:

template <class T>
inline
const T&
max(const T& x, const T& y)
{
return x < y ? y : x;
}

(btw, max is provided by the standard C++ lib, so you don't need to write
max in the first place).

Then declare your Animal interface with a virtual comparison operator:

class Animal
{
public:
virtual bool operator < (const Animal&) const = 0;
virtual ~Animal() {}
};


Then the derived animals will have to fill in a concrete implemenation:

class Dog
: public Animal
{
public:
virtual bool operator < (const Animal& y) const;
};

bool
Dog::operator < (const Animal& y) const
{
const Dog& d = dynamic_cast<const Dog&>(y);
// *this < d
return true;
}

and similarly for Giraffe.

This code satisfies all of your constraints:

int main()
{


Dog d1, d2;
Giraffe g1, g2;
Animal &a1 = d1, // a1 is a dog
&a2 = d2, // as is a2
&a3 = g1; // a3 is a giraffe

max (d1, d2); // OK

// max (d1, g1); // compilation error


max (a1, a2); // OK, same type
max (a1, a3); // run-time error
max (100, 3);

max (100+3, 3-3);
}

Now I must also confess ignorance of Eiffel. I don't know whether this is
more or less code than Eiffel requires. But either way, I don't see it as
a lot of code.

I would also be most interested in the performance of Eiffel in this
context. This is a subject that your post didn't touch on.

In the C++ example above, comparing two int's is still extremely cheap.
Comparing two animals is significantly more expensive due to the need for
the run-time checking (the virtual call and the dynamic_cast). But you
only pay for that extra expense in max when you need/want it.

-Howard

Dietmar Kuehl

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
Hi,
Greg (gmc...@my-deja.com) wrote:
: Alexei, if I'm in error in my design, then the error is originally
: Stepanov's. I refer you to the Dr Dobbs interview and Stepanov's
: discussion of mating animals.

Dump question: Have you at least glanced at this paragraph? Stepanov
explicitly says this is *NOT* what is to be done! (the exact words are
"It's definitely not what you want." but the fact that this is not the
road to a solution is clear from the beginning of the paragraph - maybe
it is not as obvious if you only pick some words from the paragraph).
... and the paragraph immediately following the paragraph about mating
animals gives a solution how to address the very problem: Use template,
not inheritance.

: I said nothing about Animal implementing a comparison operator. It may


: only define an abstract interface to it, or may not even do that.

>From your code, which you claim to produce no error, the *only*
possibility you might have tried is a comparison operator in the base
class:

Animal& a1 = Dog();
Animal& a2 = Giraffe();
Animal& a3 = max(a1, a2);

Either you can compare animals, in which case this code has to *always*
work, or you can't in which case it fails to compile (not only fail at
run-time): If you cannot compare animals, the comparison is defined for
each separate animal, eg. for dogs, but not for the base class. In this
case the above code would fail to compile. So, where is your point?
That C++ discovers the problem at compile time while Eiffel waits until
run-time? I prefer compile time error reports but milage might vary.

: My point, in part, is how difficult it is in C++ to implement a feature


: as simple as max() and have it work correctly (including error
: detection) across many circumstances.

Your point is severely flawed: You are making wrong assumptions about
how to code C++ and draw from this conclusions which are basically
arbitrary.


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dietmar Kuehl

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
Hi,
Greg (gmc...@my-deja.com) wrote:
: So you have tested this code on another compiler and it works?

Although I was sure it compiled and run as expected, I indeed tested
the code. Here is the code:

#include <stdio.h>

template <class T>
T& max(T& arg1, T& arg2)
{
return arg1 < arg2? arg2: arg1;
}

template <class T>
T const& max(T const& arg1, T const& arg2)
{
return arg1 < arg2? arg2: arg1;
}

int main()
{
printf("max: %d\n", max(1 + 2, 2 + 3));
}

Compiles and runs fine (ie. it writes "max: 5") with gcc-2.95.2 and EDG
2.44 (I don't have more compilers available). You can test that the
code compiles at <http://www.comeaucomputing.com/tryitout/>. The use of
<stdio.h> rather than <iostream> is due to not standard C++ library
shipping with the default EDG frontend (I could have used my
implementation but the results would have been harder to reproduce).

: If I can confirm this is a compiler bug, I'll retract that criticism.

Maybe you would also trust the C++ experts when they educate on what is
a compiler bug and what is not...?


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dietmar Kuehl

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
Hi,
Greg (gmc...@my-deja.com) wrote:
: The problem with a long post is that it gets tough to track all the

: details. I have a requirement (admittedly artificial) that comparing
: different species of animals should be an error. My point is that the
: definition of max() from Stepanov's interview can't detect the runtime
: error.

Your logical stays flawed (if you cannot compare animals, there should
be no comparison operator for animals in the first place) and is even
wrong as I have shown in one of my other article: If you insist on
breaking the Liskov Substitution Principle (no, Stepanov is *not*
suggesting this, he explicitly says that this is *not* what is wanted)
you can do so and define the incompatability where it belongs, namely
into the comparison operation (it is not the determination of the
maximum but rather the comparison where the error is to encapsulated).
You would use 'typeid' to do so (details can be found in one of the
other articles).

: Seriously, I see many of the things you're touting as negatives. Why


: can't I ship usable binary libraries?

Because binary libraries restrict you to a certain interface which has
to be know at the time you compile the code. You have to tell what
arguments you pass in, you have to tell what return values you get.
This is already a severe restriction! If you want this sort of
restriction, binaries can be produced. If you want the flexibility to
change the types appearing in the interface, you cannot use binaries.

My understanding of "restricted genericity" is that it tries to cope
with this problem by providing certain guarantees about the types
appearing in the interfaces from an object oriented point of view:
Where C++ uses templates to move the checks into the compiler and
thereby providing a great deal of flexibility towards the checks which
can be performed, Eiffel tries to perform the checks at compile time by
describing type commonalities and required interfaces. I'm not
sufficiently aware of the capabilities of the Eiffel checks but I'm
sure I prefer the approach where the stuff is checked at compile time!

: Why can't some level of semantics be guaranteed for me?

It is, probably more than you want to assume.

: Is it really necessary to do more work instead of less?

Are you refering to having two implementations of max() rather than one?
Does Eiffel have the concept of constants? How do you cope with them?

If you try to compare programming languages, at least try to do it on a
reasonable basis. Everything else is just ridiculous.

: Unfortunately, in my opinion the max() function I presented does fail,


: in that it doesn't detect the runtime error. If you have a compiler
: where the error is detected, or an implementation of max() which detects

: it, I'd like to see it.

See the other article I have posted which defines a comparison
operation in a base class: This is where the failure belongs! It is not
the 'max()' function which fails, it is the comparison which fails.
Since 'max()' uses the comparison, it fails as a consequence of using
this function. The implementation of 'max()' remains unchanged, of
course. Basically, you failed to provide a correct implementation of
the comparison and blamed it on the language to being bad. Interesting
approach... (not that I'm surprised about it, however: there are
definitely communities where this kind of logic is appreciated as long
as it is not applies towards their favorite toy).


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dylan Nicholson

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
In article <8u262o$a8l$1...@news.BelWue.DE>,
dietma...@yahoo.com wrote:
> Hi,
> Peter Bertok (pe...@bertok.com) wrote:
>: > template <class T>

>: > inline T& max(T& x, T& y) {
>: > return x < y ? y : x;
>: > }
>: >
>: > template <class T>
>: > inline const T& max(const T& x, const T& y) {
>: > return x < y ? y : x;
>: > }
>
>: Not only will those two cause ambiguity errors,
>
> Can you please spell out any situation where these two definitions
might
> provide an ambiguity error? Just one! (hint: these functions are under
> no circumstances ambiguous). ... and don't try such a lame approach as
> "my [V]C++ compiler tells me that these two function are ambiguous"!
> Yes, there are broken compilers out there.
>
Interesting, VC++ actually works fine as long as you only have one
definition of max available (the one without the consts) - I assume
because it allows both "int" and "const int" as a type for a template
parameter. In fact in the example given it's not even useful to have
both versions of max - they both do exactly the same thing (the version
of operator < called will depend on whether the template parameter
itself is const or not). Is this non-compliant behaviour too?

Dylan

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Howard Hinnant wrote:

> In article <8u3pqs$8ia$1...@nnrp1.deja.com>, Greg <gmc...@my-deja.com>
wrote:

> | My point, in part, is how difficult it is in C++ to implement a feature


> | as simple as max() and have it work correctly (including error
> | detection) across many circumstances.

> Setting aside for the moment questions of design, obtaining the


> behavior you desire in C++ does not seem like that much work to me.

> First off, pair your max template down to just:

> template <class T>


> inline
> const T&
> max(const T& x, const T& y)
> {
> return x < y ? y : x;
> }

Agreed. It is a design error to allow < to modify its operands,
although neither C++, nor apparently Eiffel, can prevent it. I see no
need for max to support design errors, however.

> (btw, max is provided by the standard C++ lib, so you don't need to
> write max in the first place).

> Then declare your Animal interface with a virtual comparison operator:

> class Animal
> {
> public:
> virtual bool operator < (const Animal&) const = 0;
> virtual ~Animal() {}
> };

Two comments:
- First, I wouldn't make operator< virtual, or even a member. I'd
define a virtual function compare (for example), which returned an
int <, == or > 0, and call that from operator< (and operator>, and
operator==, ...).
- The virtual function shouldn't be public -- in C++, virtual
functions should never be public.

The latter is especially useful if we decide that as a precondition,
the two actual types must be the same. We could then write:

inline bool
Animal::compare( Animal const& other ) const
{
if ( typeid( *this ) != typeid( other ) ) {
throw invalid_argument( "only like animals can be compared"
) ;
}
return doCompare( other ) ;
}

[...]

> I would also be most interested in the performance of Eiffel in this
> context. This is a subject that your post didn't touch on.

Probably because it can be extremely implementation dependant. And
the question concerned correct code.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Dietmar Kuehl wrote:

> Greg (gmc...@my-deja.com) wrote:
>: Alexei, if I'm in error in my design, then the error is originally
>: Stepanov's. I refer you to the Dr Dobbs interview and Stepanov's
>: discussion of mating animals.

> Dump question: Have you at least glanced at this paragraph? Stepanov
> explicitly says this is *NOT* what is to be done! (the exact words are
> "It's definitely not what you want." but the fact that this is not the
> road to a solution is clear from the beginning of the paragraph - maybe
> it is not as obvious if you only pick some words from the paragraph).
> ... and the paragraph immediately following the paragraph about mating
> animals gives a solution how to address the very problem: Use template,
> not inheritance.

But this is only a sometimes solution. It doesn't work if you need
the run-time dispatch.

Whether this is a serious object with regards to < on Animals, I
haven't the slightest idea, since I can't begin to fathom what <
should mean when applied to Animals. There are many cases where it is
not an objection, and the compile time checking of templates should be
favored -- from the posted Eiffel, I think this can also occur in
Eiffel as well, if for example, Animal doesn't derive from Comparable,
but Dog and Cat do. But I don't know Eiffel enough to be sure. There
are, of course, cases where the dynamic dispatch is essential, and in
this case, your argument (or Stepanov's) concerning templates instead
of inheritance doesn't hold.

Even with inheritance, however, I don't see the problem as
particularly difficult in C++.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Greg wrote:

> The problem with a long post is that it gets tough to track all the
> details. I have a requirement (admittedly artificial) that comparing
> different species of animals should be an error. My point is that
> the definition of max() from Stepanov's interview can't detect the
> runtime error.

Which it shouldn't. If comparing different species of animals should
be an error, then it is the comparison operator which should fail
(which of course means that max will fail, but the essential
precondition check doesn't belong in the generic max -- neither in C++
nor in Eiffel).

The standard way of writing this in C++ is:

class Animal
{
public:
bool compare( Animal const& other ) const


{
if ( typeid( *this ) != typeid( other ) ) {

throw std::invalid_argument( "error message" ) ;
}
return doCompare( other ) ;
}

protected: // or private, but protected is more Eiffel-like.
virtual bool doCompare( Animal const& other ) const = 0 ;
} ;

It may be a little more typing than for Eiffel, but the difference
doesn't seem really significant.

> > > Further, I'd like to see a solution that works even if the
> > > implementation of max() changes to use operator> instead of
> > > operator<.

The usual solution in C is to just implement operator<, and to pick up
the other operators by using std::rel_ops. I tend to boiler plate all
of the operators, but this is not a necessity.

> [..]

> > THE C++ EXAMPLE DOES NOT FAIL IN THE CASE OF POLYMORPHISM.

> Unfortunately, in my opinion the max() function I presented does


> fail, in that it doesn't detect the runtime error. If you have a
> compiler where the error is detected, or an implementation of max()

> which detects it, I'd like to see it.

I don't understand what you mean that C++ doesn't detect the run-time
error. If you mean that it is possible to write C++ code that ignores
the run-time error, and generates undefined behavior instead, this is
true. You can write bad programs in any language, and C++ certainly
doesn't go out of its way to make bad programming difficult. But the
normal idiom in such cases is to use dynamic_cast, or if the equality
of the types is an absolute imperative (which sounds a bit artificial
to me), then a pre-condition check as above.

If your goal is simply to show that language A is better than language
B, it is easy to go to the equivalent of the C obfuscated code site
for language B, for your examples. But what does that prove?

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Dietmar Kuehl wrote:

> > Seriously, I see many of the things you're touting as
> > negatives. Why can't I ship usable binary libraries?

> Because binary libraries restrict you to a certain interface which
> has to be know at the time you compile the code. You have to tell
> what arguments you pass in, you have to tell what return values you
> get. This is already a severe restriction! If you want this sort of
> restriction, binaries can be produced. If you want the flexibility
> to change the types appearing in the interface, you cannot use
> binaries.

This seems to be drifting into implementation details that are
irrelevant. First, of course, there is nothing in C++ per se that
prevents you from shipping binary libraries. In fact, with at least
one compiler (Visual Age), you can, and the standard requires it,
although most implementations aren't there yet.

But in another sense, whether you can or cannot ship binary libraries
has nothing to do with the original question. The problem presented
was admittedly artificial. Dietmar's points mainly address this
artificiality -- in a well written application, the problem (as
stated) generally shouldn't occur, because as Dietmar points out, it
represents a flagrant violation of good object oriented design, or for
that matter, design in general. Which is an important point -- C++
support for the better design is excellent. Reality, of course,
sometimes requires compromises with optimal design, and I have
occasionally had cases where the alternatives to "loosing the type"
are worse. In such cases, something like the proposed problem can
occur. But C++ solves them as well.

I don't want to criticize Eiffel. I don't know it very well, but I
suspect that, given my orientation, I would like it a lot. Never the
less, for the given problem, C++ offers the necessary tools, in a form
that isn't too difficult to use. It doesn't require them, but I don't
think that Eiffel requires you to program the preconditions either,
even if it does encourage it.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Peter Bertok wrote:

> Not only will those two cause ambiguity errors, there is also this
> situation:

> const int& foo = max( 1 + 2, 2 + 3 );

> The error is usually something like "Cannot return the address of a
> temporary."

Which isn't an error, but only a warning, and not required.

> In other words, there should also be a THIRD max:

> template <class T>


> inline const T max(const T& x, const T& y) {
> return x < y ? y : x; // Returns a copy of x or y, not a
> reference.
> }

More likely, this should be the only version.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Dietmar Kuehl wrote:

> Peter Bertok (pe...@bertok.com) wrote:
>: > template <class T>


>: > inline T& max(T& x, T& y) {
>: > return x < y ? y : x;
>: > }

>: > template <class T>
>: > inline const T& max(const T& x, const T& y) {
>: > return x < y ? y : x;

>: > }

>: Not only will those two cause ambiguity errors,

> Can you please spell out any situation where these two definitions might
> provide an ambiguity error? Just one! (hint: these functions are under
> no circumstances ambiguous). ... and don't try such a lame approach as
> "my [V]C++ compiler tells me that these two function are ambiguous"!
> Yes, there are broken compilers out there.

Well, there is an obvious ambiguity problem, but you don't need the
two functions to show it:

max( 0L , 1.5 ) ;

>: there is also this situation:

>: const int& foo = max( 1 + 2, 2 + 3 );

>: The error is usually something like "Cannot return the address of a
>: temporary."

> This is an error stemming from a broken compiler!

It's usually a warning, not an error. And it's usually a worthwhile
warning.

> The arguments
> passed to the 'max()' functions are bound to a 'int const&' which
> can be returned from the 'max()' function without problem. The only
> thing which might cause a problem is the life time of the object
> bound to 'foo': It is possible that it might not be accessible in
> the next statement (I'm not sure about the details but I wouldn't
> personally try to play tricks on this one).

It's end of the full statement.

> As long as you use the
> result only in the same statement this is no problem, however:
> Temporaries are destructed at the end of the enclosing full
> expression.

>: In other words, there should also be a THIRD max:

>: template <class T>
>: inline const T max(const T& x, const T& y) {
>: return x < y ? y : x; // Returns a copy of x or y, not a
>: reference.
>: }

> No, you don't need a third a version. The two versions given above
> are just fine.

But in practice, you only want the const version, I would think. And
in practice, I'd go for the return by value (and not by reference) --
it's safer with regards to the lifetime of temporary issue, and I
think that the compiler should always be able to optimize the
additional copies out.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Dietmar Kuehl wrote:

> Greg (gmc...@my-deja.com) wrote:
>: So you have tested this code on another compiler and it works?

> Although I was sure it compiled and run as expected, I indeed tested
> the code. Here is the code:

> #include <stdio.h>

> template <class T>
> T& max(T& arg1, T& arg2)
> {
> return arg1 < arg2? arg2: arg1;
> }

> template <class T>
> T const& max(T const& arg1, T const& arg2)
> {
> return arg1 < arg2? arg2: arg1;
> }

> int main()
> {
> printf("max: %d\n", max(1 + 2, 2 + 3));
> }

I wouldn't expect a problem with any compiler for this. While not
correct, the case where I could imagine a problem would be with things
like:

static const int limit = -100 ;

int
f( int i )
{
return max( limit , i ) ;
}

And lets face it, the rules are complicated.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

Anatoli Tubman

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
In article <8u3s6i$a3l$1...@nnrp1.deja.com>,

Greg <gmc...@my-deja.com> wrote:
> This is just one example of a general concept. Certainly in the real
> world there can be operations that can only be applied to objects of
> the
> same type, and mixing types is an error.

Please provide an example.

> I'm simply saying this is a
> circumstance where the C++ version doesn't detect this error and the
> Eiffel version does.

Eiffel doesn't either, all claims to the contrary in Eiffel literature
notwithstanding.

--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't

James Kanze

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
Greg wrote:

> In article <3A049D0A...@ihug.co.nz>,
> Ross Smith <ros...@ihug.co.nz> wrote:
> > Greg wrote:
> [...]
> > > // Assume Dog and Giraffe are descendents of Animal, and that
> > > // it should be an error to compare dogs to giraffes.
> > > //
> > > Animal& a1 = Dog;
> > > Animal& a2 = Giraffe;
> > > Animal& a3 = max (a1, a2); // no error detected.

> > This is the fault of whoever designed the classes. If
> > Animal<Animal is legal but Dog<Giraffe isn't, the Liskov
> > substitution principle has been broken. I don't think you can
> > blame the language for a design cockup as egregious as that.

> The design isn't bad, it's artificial :-)

It's bad in the sense that it violates the LSP. Whether this is
justified or not, we cannot tell without more of the environment, but
it generally takes some pretty strong external constraints for me to
accept a violation of the LSP.

> This is just one example of a general concept. Certainly in the real
> world there can be operations that can only be applied to objects of
> the same type, and mixing types is an error.

It's true that the real world doesn't always conveniently conform to
our ideal OO models:-). Whenever possible, however, if mixing types
is an error, the best programming solution is not to mix them, rather
than generating a run-time error when they are mixed. Of course, not
all programming languages support this.

> I'm simply saying this
> is a circumstance where the C++ version doesn't detect this error
> and the Eiffel version does.

And we're simply saying that this is because you know how to write the
code so that it is detected in Eiffel, and you don't know how to do it
in C++. In my case, the opposite would be true: any C++ code I wrote
would detect the error, where as I haven't the slightest idea how to
detect it in Eiffel. I don't consider this a problem with Eiffel,
however, but with my very imperfect knowledge of the language.

> Take a look at Stepanov's article, especially the Dr Dobbs article
> and the discussion on Animal there.

> > Polymorphism and comparison operators generally don't play well
> > together regardless of your choice of language. Where it's
> > practical to provide comparisons for a polymorphic class
> > hierarchy, it's usually because the comparison is dependent only
> > on the common properties available through the base class, so a
> > max() operation that devolves to a base class comparison is the
> > Right Thing. When that _isn't_ the right thing, the general
> > solution requires multiple dispatch, which I don't believe is
> > supported by Eiffel either. (You can get a pretty good
> > approximation in C++ with a bit of extra work; I don't know enough
> > about Eiffel to hazard any guess as to whether it's easier or
> > harder there.)

> In C++, comparison operators are just another function. I could
> extrapolate your comment to mean "in C++, polymorphism and
> functionality don't mix well."

Operators aren't totally like functions in C++, although they can be
implemented as such. Operators like < typically aren't, though,
because in so far as they are like functions, they are like
free-standing functions, and not member functions. And free-standing
functions, in C++, don't support polymorphism. But the problem here
is more the orthogonality of <. For < to work well in a polymorphic
environment, it must dispatch on both operands. C++ (and Eiffel as
well, I think) doesn't support double dispatch, so < and polymorphism
don't work well together.

> I've had no problem getting this to work in Eiffel. I think it can
> be made to work in C++, but instead of being automatic, it's a lot
> of effort.

I had no problem getting this to work in C++ (at least to the given
specification -- one would generally design in a bit more safety in
C++). I haven't the slightest idea how to get it to work in Eiffel.

Could it be simply that you know Eiffel better than C++, whereas I
know C++ better than Eiffel.

> Devolving the functionality to the base class could definitely be
> the Wrong Thing in many instances.

And yet Eiffel systematically devolves part of the functionality (the
pre- and post condition checks) to the base class:-). Rightly so,
IMHO, but I'm hardly going to criticize C++ for doing the same thing.

> > > And an even more simple test case:

> > > cout << max (100, 30) << endl;

> > > generates an error with my C++ compiler (VC++ 6.0, for the
> > > record.) The error? The compiler can't determine which of the
> > > two versions of max should apply.

> > Compiler bug.

> > Do you really think it's reasonable to criticise a language
> > without even learning enough about it to tell the difference
> > between a language restriction and a compiler bug?

> Show me which compilers this does work on and I'll retract this
> criticism. I'd like to see the test program and its output.

The C++ standard says it should work. It works with g++ (version
2.95.2), which is the only C++ compiler I have on this machine.

> > > Is it possible to correct these deficiencies in the C++
> > > implemenation? I believe so, but it would require access to all
> > > of the source for all of the classes involved.

> > Which is also true of the Eiffel solution you propose, which
> > imposes the drastic rule that all classes that implement operator<
> > have a common base class.

> The "drastic rule" is part of the original requirements. As I said,
> this is an artificial example of a general principle.

It is a general requirement that all classes which support comparison
have a common base class? I can understand the utility, and I don't
find it a drastic rule, but it does impose a lot of additional
standardization and implementation effort.

> > I'm not intimately familiar with Eiffel so I waon't try to
> > criticise your Eiffel implementation, beyond pointing out that
> > it's asymmetric (the classic problem with operators in "silver
> > bullet" languages that try to make everything OO), and that your
> > description of the properties that should be required of the
> > less-than operator is very confused (the definition on the web
> > page you point to gets it right, though, so I assume the mistakes
> > are yours rather than Eiffel's).

> Thanks, if I have a chance, I'll try to make it less confused.

I think that the asymetry is the main criticism, and it is basic in
the design. Unless the language supports multi-dispatch, a
dynamically dispatched operator< will be inherantly asymetric. In
C++, if you use templates as a form of static OO, you have
multi-dispatch -- the template function generated will depend on the
types of all of its parameters. Templates are also very powerful for
imposing type constraints: object A must have the same type as object
B. This power isn't free: the resolution of any polymorphism occurs
at compile time, not at run-time. But if at all possible (and it
isn't always possible), if your invariants involve type relationships
between several objects, templates are the preferred solution, since
they will enforce the type relationships at compile time.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 7, 2000, 12:19:35 AM11/7/00
to
Dietmar Kuehl wrote:

It's difficult to be categorical in the absense of an actual
application. If part of the precondition of comparing animals is a
type relationship between the two animals, the preferred solution is
certainly to not support comparison at the base class level. But
there are many practical situations where this causes more problems
than the alternatives.

It is important in such cases that the possibility of failure (and how
it is reported) be part of the contract.

It's not necessarily a design error. Not having the slightest idea
what this class is supposed to do, I can't really say that it is a
design error. But in general, a function, regardless of the class it
belongs to, can have preconditions, and these preconditions can
involve type information. As usual, the earlier you detect an error,
the better, and so a compile time error is to be preferred. But there
are cases where it isn't possible.

At any rate, I don't see the slightest problem in implementing this in
C++, so I don't see where the problem is.

>: max (100, 3);
>
>: Further, I'd like to see a solution that works even if the
>: implementation of max() changes to use operator> instead of operator<.

> That's just boring: Add another template argument for a comparator
> object.

Since one of the standard idioms in C++ is using std::rel_ops, I don't
see where there is the slightest problem. Of course, since max is a
standard function, it isn't liable to change, so the problem doesn't
come up in this case.

>: The max() function guarantees that both parameters are compatible and
>: can be compared legally. This is true even in the face of polymorphism,
>: where the C++ implementation fails. In other words, it works for all
>: the cases I've outlined above ( even max (100, 3) :-)

> Using a simple base class 'comparable' the exact same thing can be
> implemented in C++, too. So where is your point?

To date, he's proven that he knows Eiffel better than C++. And that
you can generally find a better solution in a language you know well
than in one you don't know. I wouldn't dispute either point.

>: Does the Eiffel implementation have shortcomings that the C++
>: implementation does not?

> Since I don't know Eiffel, this is hard to tell. The crucial part of
> the C++ 'max()' function, is that the type returned is the type of
> the arguments, not some other type, eg. a base class.

His Eiffel implementation seemed to have some sort of assertion to
that effect as well.

> It is actually
> equally important, that the arguments are not required to be derived
> from some specific class/interface/however you call it, like eg. the
> "COMPARABLE": They are only required to conform to a certain abstract
> concept, for 'max()' is the concept of being "less than comparable".

Why is it important not to give this concept a name, and require that
the class state explicitly that it implements it.

Remember the problems we have (and to a degree still have) because
auto_ptr is not copy assignable, although it implements the operator=
for other purposes. If we had some sort of abstract base class that
specified "copy assignable", the problem would never have arisen;
auto_ptr wouldn't have derived from this class.

In practice, of course, we can't do this and maintain any form of C
compatibility. int will not derive from Comparable. And judging from
the mess of it made in Java, it is better to not do anything in this
regard than to do it halfway.

In practice, also, this ends up with all objects deriving from Object,
or some other common base. In theory, this is not a problem, but in
practice, every time this happens, far too many people end up using it
in far to many cases, to avoid having to specify their interfaces
rigorously. With the results that we end up with run-time typing
errors instead of compile-time typing errors. It's not easy to do
right. C++ at least requires you to try, and gives you a fair number
of tools to help.

> The exact meaning of what "less than comparable" means is, BTW, well
> defined by the C++ standard (20.1.2 - Less than comparison
> [lib.lessthancomparable] which basically says that 'operator<()' has
> to be a strict weak ordering).

>: Other than that, I've not yet found a way to break max() so that either
>: a coding error is undetected or a legal comparison can't be performed.

> In C++ the following works:

> struct Dog {
> void barf() const { std::cout << "barf!\n"; }
> bool operator< (Dog const&) const { return true; }
> };

> max(Dog(), Dog()).barf();

> For generic programming it is crucial that this works, in particular
> it is necessary that the return type of 'max()' is of type 'Dog'.

No. Generic programming works with dynamic type checking as well.
It's a lot more dangerous, but it works.

> If it
> does work for Eiffel, Eiffel might be usable to do generic
> programming. However, this is not the only restriction. It is also
> necessary that certain operation return types depending on the exact
> type of the function arguments, not just some type know to a base
> class.

But it isn't necessary for this dependance to be evaluated at compile
time.

> For example, the function accessing an element identified by an
> iterator (in C++ 'operator*()') has to return a type depending on the
> underlying sequence: If the iterator is eg. moving over a sequence of
> 'int's, the result of accessing an element has to be a 'int', too.

No. It has to return a type that can be used as an int. Generic
programming works in Smalltalk, for example.

I prefer static type checking whenever possible, but it is a question
of safety, not generic programming.

> If
> the underlying sequence has some other type, the function accessing the
> element has to return an object (or reference to such an object) of the
> corresponding type.

It will, at least for the dynamic type. The correct static type is
necessary for compile time type checking, but generic programming will
work even in dynamically typed languages.

[...]


>: c) As is common with C++, language features provide primitive
>: mechanisms for implementing a facility, in this case templates for
>: implementing generic programming. However, because the mechanisms are
>: primitive, it is difficult or impossible to come up with robust general
>: solutions. Further, the author of these solutions have to go an
>: enormous amount of work to get the details right.

> This is a religous approach to comparing computer languages. Since
> it makes not sense to discuss on such a level I'm not going to
> comment on this except stating that there are other religions out
> there which have a different view on this.

I think it was once Stroustrup himself who said that Smalltalk was a
better Smalltalk than C++ was. If you want exactly what Eiffel
offers, no more, no less, then I suspect that Eiffel will be a better
solution than C++. C++'s advantage is that it doesn't constrain you.
I generally use a programming by contract idiom in C++ that would
probably map very closely to Eiffel. Maybe I should be using Eiffel,
but in my case, generally != always. It's easy enough to do
programming by contract in C++, and I can also use many other idioms
when appropriate.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

Jim Cochrane

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <3A068D47...@dresdner-bank.de>,
James Kanze <James...@dresdner-bank.de> wrote:
>Howard Hinnant wrote:
>
> ...

>Two comments:
> - First, I wouldn't make operator< virtual, or even a member. I'd
> define a virtual function compare (for example), which returned an
> int <, == or > 0, and call that from operator< (and operator>, and
> operator==, ...).
> - The virtual function shouldn't be public -- in C++, virtual
> functions should never be public.

Why should a virtual function never be public?
--
Jim Cochrane
j...@dimensional.com

Greg

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <8u650q$dc$1...@nnrp1.deja.com>,

Anatoli Tubman <ana...@my-deja.com> wrote:
> In article <8u3s6i$a3l$1...@nnrp1.deja.com>,
> Greg <gmc...@my-deja.com> wrote:
> > This is just one example of a general concept. Certainly in the real
> > world there can be operations that can only be applied to objects of
> > the
> > same type, and mixing types is an error.
>
> Please provide an example.

No problem. Say you have a system that supports ASCII and EBCDIC-based
strings. Both could be derived from the same base class, yet clearly
comparing the two types directly is a serious error. At the same time,
certain parts of the system may have no need to know the implementation
of the string, and only want to manipulate it through the base type.

>
> > I'm simply saying this is a
> > circumstance where the C++ version doesn't detect this error and the
> > Eiffel version does.
>

> Eiffel doesn't either, all claims to the contrary in Eiffel literature
> notwithstanding.
>

Here's an example that does what I'm claiming. In this case, I'm
creating dogs that are compared by weight and giraffes that are
compared by height. This (very artificial) example doesn't allow the
two types to be mixed. I could change the example by placing the
objects into an array of ANIMAL and get the same level of error
detection.

To compile this example:
1) Download and install the latest version of SmallEiffel from
http:\\smalleiffel.loria.fr
2) Copy the source for each class into it's own file, with the same
filename as the class.
3) run the command:
compile max_test
4) This will create an executable named max_test1.exe. Running this as-
is will produce no errors. Uncomment the source code lines that
indicate compile-time and run-time errors to see the error detection
happen.

For grins I tossed in some shared behavior. All animals have generic
names.

As a side note, the optimized executable for the Eiffel program is
about 20% smaller than the C++ equivalent (compiled optimized for
size). The size difference may mostly be due to the use of streams for
I/O in the C++ program.

Compilation errors will be obvious. Runtime errors will start with text
like:
Line: 15 column 22 in .\dog.e.
*** Error at Run Time *** :
Target is not valid (not the good type).

--
http://homestead.deja.com/user.gmc333/index.html

--------
class ANIMAL
inherit COMPARABLE
feature
name : STRING
end

-------

class DOG
inherit ANIMAL
creation make

feature make is
do
name := "DOG"
make_weight.next
weight := make_weight.last_double * 50 + 2.0
end

infix "<" (other: like Current) : BOOLEAN is
do
Result := weight < other.weight
end

weight: DOUBLE

feature {NONE}
make_weight: STD_RAND is once !!Result.make end
end

-------

class GIRAFFE
inherit ANIMAL
creation make

feature make is
do
name := "GIRAFFE"
-- make a height between 3 and 10 meters
make_height.next
height := make_height.last_double * 7.0 + 3
end

infix "<" (other: like Current) : Boolean is
do
Result := height < other.height
end

height: DOUBLE

feature {NONE} -- our private parts
make_height: STD_RAND is once !!Result.make end
end

-------

class MAX_TEST
creation make
feature

max (first: COMPARABLE; other: like first) : like first is
require other_exists: other /= Void
do
Result := first
if first < other then
Result := other
end
end

make is
local
giraffe_1, giraffe_2 : GIRAFFE
dog_1 :DOG
a1, a2 , a3: COMPARABLE

giraffe_3: GIRAFFE
do
io.put_string (max(6, 2).out + "%N")

!!giraffe_1.make
!!giraffe_2.make
!!dog_1.make

a1 := giraffe_1
a2 := giraffe_2

a3 := max (a1, a2) -- OK to compile; comparing two COMPARABLES
-- OK at runtime; Eiffel knows we have two
giraffes

giraffe_3 ?= a3 -- assignment attempt: Eiffel's way of doing a
dynamic cast

-- dog_1 ?= a3
-- check
-- dog_1 /= Void -- Runtime error: assignment attempt failed from
type conflict
-- end

io.put_string ("giraffe_3 = " +giraffe_3.height.to_string+ "%N")

-- Now let's try comparing giraffes and dogs
--
-- a1 := max (giraffe_1, dog_1) -- Compilation error! type conflict

a1 := dog_1 -- try bypassing the compilation error
-- a3 := max (giraffe_1, a1) -- Compilation error! Type conflict

a2 := giraffe_1
-- a3 := max (a1, a2) -- Runtime error! still mixing dogs and giraffes
end

end -- MAX_TEST

llew...@edevnull.com

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to

First, a general note about your challenge:

I believe you deliberately designed it to make Eiffel appear
superior to C++. I believe that whatever our replies to you, you
intend to go away satisfied that Eiffel has 'won' your challenge.

Perhaps it is unfair of me to judge you so harshly, but that is what
I see.

A 'challenge' of this nature is a cruel farce that does no good and
may do much harm.

Greg <gmc...@my-deja.com> writes:

[snip]
> I've read through the interviews, and in them are three tests that Dr.
> Stepanov might apply to qualify a language for GP. I'd like to discuss
> one of these tests, how Dr. Stepanov implemented it in one of the
> interviews, and how the same test can be implemented in Eiffel. (In
> another thread, I debated with a couple of people over the merits of
> C++ vs Eiffel's generic programming facilities.)

And there will be no end to this debate until the debaters admit that

(a) Both languages have important advantages and disadvantages in
this area.

(b) This area is not simple enough for there to be a clear winner.

>
> First, here's a version of Dr. Stepanov's implementation.


>
> template <class T>
> inline T& max(T& x, T& y) {
> return x < y ? y : x;
> }
>
> template <class T>
> inline const T& max(const T& x, const T& y) {
> return x < y ? y : x;
> }
>

[snip]
>
> Now here's an implementation of max() in Eiffel:


>
> max (x: COMPARABLE; y : like x) : like x is

> require y_exists: y /= Void
> do


> Result := x
> if x < y then
> Result := y
> end
> end
>

[snip]


> And an even more simple test case:
>
> cout << max (100, 30) << endl;
>
> generates an error with my C++ compiler (VC++ 6.0, for the record.) The
> error? The compiler can't determine which of the two versions of max
> should apply.

*shrug* My compiler likes that test case just fine.

>
[snip]


>
> I'd like to challenge you to present an implementation that produces a
> correct answer in the face of polymorphism, const and non-const
> variables, and max (100, 3). (Keep in mind that I'm looking at cases
> where "the correct answer" is compile-time or run-time error detection
> when the code is incorrect.)
>
> class Animal ( ... };
> class Dog : public Animal { ... };
> class Giraffe : public Animal { ... };
> ...
> Dog d1, d2;
> Giraffe g1, g2;
> Animal &a1 = d1, // a1 is a dog
> &a2 = d2, // as is a2
> &a3 = g1; // a3 is a giraffe
>
> max (d1, d2); // OK
> max (d1, g1); // compilation error
> max (a1, a2); // OK, same type
> max (a1, a3); // run-time error

> max (100, 3);
>
> Further, I'd like to see a solution that works even if the
> implementation of max() changes to use operator> instead of operator<.
>

> On to the Eiffel example. Note is that in the declaration of max(), the
> first parameter is declared to be a COMPARABLE. This class defines an
> interface and a contract for the common comparison operators < > /=
> (not-equal) = (is equal) <= and >=. Only the operator "<" is left
> unimplemented.

I am certain I have used std::max() on classes that had *only*
operator<() (not greater-than, not-equal etc). One of the principles
of generic programing is to place as few restrictions on the type
parameters as necessary - your example implies that effiel makes it
difficult to minimize the requirements on the type parameters. My
point is that what you describe as a disadvantage, can often be an
advantage.

>
> Inheriting from COMPARABLE forces the implementor to write a "<"
> function that conforms to the contract. The contract states in part
> that asymmetry must hold for "<". In other words,
> a < b iff b >= a
> and
> a < b equals not b < a
[snip]


> Does the Eiffel implementation have shortcomings that the C++

> implementation does not? For one thing, Eiffel doesn't have the same
> concept of immutibility (ie "const-ness") that C++ does. So for better
> or worse, there is no way to guarantee that max() won't modify it's
> parameters. On the other hand, one could supply contracts for the
> classes being compared, forcing them to check that they have not been
> modified after the comparison.

Why do you bring up this issue as a deficiency of Eiffel if it is so
easily solved by features Eiffel already has? C++ has an explicit
feature for enforcing immutability. In Eiffel, you build it with a
contract and a trivial amount of code. Eiffel has explicit
features for contracts. In C++, you build them with trivial amounts
of templated code. In both cases poorly educated programmers insist
on seeing language defeciencies, while good programmers concentrate
on learning how to use the features the languagues already
provide. Simply because language X does not provide an explicit
unambigous keyword for feature Y does not mean that doing Y in
language X is difficult or impossible.

>
> Other than that, I've not yet found a way to break max() so that either
> a coding error is undetected or a legal comparison can't be performed.
>
>

> In conclusion:

Conclusion? This is what you intended to discover from the start.

> a) Eiffel's implementation of constrained genericity and covariance
> provides superior generic programming facilities over C++ templates.
>

> b) In this case at least, the implementation of max() does more with
> far less source code than does any C++ implementation (ironic for a
> language that is often criticized for being verbose, being compared to
> one often praised for its brevity).

*shrug* How often are languages genuinely good at what they are
praised for? IMO, seldom. How often are attributes a language is
widely insulted for genuine problems? IMO, seldom.

[snip]

Greg

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <hinnant-0511...@ith3-456.twcny.rr.com>,

hinnant@anti-spam_metrowerks.com (Howard Hinnant) wrote:
> In article <8u3pqs$8ia$1...@nnrp1.deja.com>, Greg <gmc...@my-deja.com>
wrote:
>
> | My point, in part, is how difficult it is in C++ to implement a
feature
> | as simple as max() and have it work correctly (including error
> | detection) across many circumstances.
>
> Setting aside for the moment questions of design, obtaining the
behavior
> you desire in C++ does not seem like that much work to me.
>
> First off, pair your max template down to just:
>
> template <class T>
> inline
> const T&
> max(const T& x, const T& y)
> {
> return x < y ? y : x;
> }

Howard: thanks for posting code instead of flames :-). I got your
example to compile. I think Stepanov originally included the non-const
version for completeness; perhaps it is needed to cover the case where
operator< needs to modify the target class in some way (computing and
caching an intermediate result for comparison?) Regardless, I tend to
agree that in general the non-const version should not be needed (and
indeed isn't part of the standard library).


>
> (btw, max is provided by the standard C++ lib, so you don't need to
write
> max in the first place).

True. As does Eiffel. I was looking at this as an interesting exercise.


>
> Then declare your Animal interface with a virtual comparison operator:
>
> class Animal
> {
> public:
> virtual bool operator < (const Animal&) const = 0;
> virtual ~Animal() {}
> };
>

> Then the derived animals will have to fill in a concrete
implemenation:
>
> class Dog

>: public Animal
> {
> public:


> virtual bool operator < (const Animal& y) const;
> };
>
> bool
> Dog::operator < (const Animal& y) const
> {
> const Dog& d = dynamic_cast<const Dog&>(y);
> // *this < d
> return true;
> }
>
> and similarly for Giraffe.
>
> This code satisfies all of your constraints:
>
> int main()
> {

> Dog d1, d2;
> Giraffe g1, g2;
> Animal &a1 = d1, // a1 is a dog
> &a2 = d2, // as is a2
> &a3 = g1; // a3 is a giraffe
>
> max (d1, d2); // OK

> // max (d1, g1); // compilation error


> max (a1, a2); // OK, same type
> max (a1, a3); // run-time error
> max (100, 3);

> max (100+3, 3-3);
> }
>
> Now I must also confess ignorance of Eiffel. I don't know whether
this is
> more or less code than Eiffel requires. But either way, I don't see
it as
> a lot of code.

A couple of interesting points between your solution and the Eiffel
solution. In the C++ solution, the type conformity check is pushed down
to the classes to be compared. In Eiffel, the check is in the max()
operator instead. While it should be possible to modify the C++ max()
so that it does the check, it doesn't appear to have been done this way
for the library implementation of max(). So every class has to do the
check itself, which is a little extra code everywhere.

Leveraging COMPARABLE in Eiffel means that all the target classes need
only implement operator<. If you want the full complement of comparison
operators in C++, you have to implement them all. (I think another
poster referred to a solution to this; when I have time I'll check it
out. But it doesn't seem to be part of my C++ compiler...)

Sharing the implementation of max(), Animal, Dog, Giraffe, etc. means
writing include files, and in the case of templates, it usually means
distributing at least some of the implementation in those include
files. Yet more code to write. Eiffel, like Java, provides tools for
automatically extracting the interface of a class, so no include files
(I believe the Java developers lifted this concept from Eiffel in the
first place.)

So while C++ is more terse "in the small," I think as systems scale up,
Eiffel can actually accomplish less than more. No, I don't have hard
figures on this, just a gut feel after working with both languages for
a considerable amount of time...

>
> I would also be most interested in the performance of Eiffel in this
> context. This is a subject that your post didn't touch on.
>

> In the C++ example above, comparing two int's is still extremely
cheap.
> Comparing two animals is significantly more expensive due to the need
for
> the run-time checking (the virtual call and the dynamic_cast). But
you
> only pay for that extra expense in max when you need/want it.
>

I've seen benchmarks that show Eiffel performance being on a par with
C++. In this case, Eiffel would certainly have performance equal to
C++. By default Eiffel variables are references, and everything being
passed around in a call to max() are pretty lightweight.

As for the overhead of the runtime check, I've looked at how this is
implemented in SmallEiffel. It amounts to the comparison of two
integers via pointers. Pretty cheap. And if you turn on all
optimizations, you supress these runtime checks. The idea being that
once you've finished testing, you can let the code go all-out. The
SmallEiffel compiler is an amazing piece of technology.

Greg
--
http://homestead.deja.com/user.gmc333/index.html


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Greg

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <8u4crk$2hl$1...@news.BelWue.DE>,
dietma...@yahoo.com wrote:
> Hi,

> Greg (gmc...@my-deja.com) wrote:
>: Alexei, if I'm in error in my design, then the error is originally
>: Stepanov's. I refer you to the Dr Dobbs interview and Stepanov's
>: discussion of mating animals.
>
> Dump question: Have you at least glanced at this paragraph? Stepanov
> explicitly says this is *NOT* what is to be done! (the exact words are
> "It's definitely not what you want." but the fact that this is not the
> road to a solution is clear from the beginning of the paragraph -
maybe
> it is not as obvious if you only pick some words from the paragraph).
> ... and the paragraph immediately following the paragraph about mating
> animals gives a solution how to address the very problem: Use
template,
> not inheritance.

Dietmar: I think the key quote from the interview is:
"Then you derive giraffe from animal and, of course, it has a function
mate where giraffe mates with animal and returns an animal. It's


definitely not what you want."

He does go on to say that the solution is to use templates. Templates
are the generic programming mechanism in C++, correct?

OK, what I'm looking for here is a solution that allows inheritance,
and allows giraffes to mate with giraffes, even if the objects are
bound to declarations of animals. In other words, in my example of max
() the compiler and run-time checks should guarantee that a giraffe is
never compared with a non-giraffe.

The Eiffel solution gives me this automatically and easily. The C++
solution (which I agree can't be accomplished without templates) takes
a conscious effort on the part of the programmer to provide these
checks and detect the error.

bran...@cix.compulink.co.uk

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
ku...@ramsen.informatik.uni-konstanz.de (Dietmar Kuehl) wrote (abridged):

> Either you can compare animals, in which case this code has to *always*
> work, or you can't in which case it fails to compile (not only fail at
> run-time): If you cannot compare animals, the comparison is defined for
> each separate animal, eg. for dogs, but not for the base class. In this
> case the above code would fail to compile. So, where is your point?

I'm not an Eiffel expert, but I'll try to translate the Eiffel type rule
into C++.

In Eiffel, code like this will compile:

Animal& a1 = Dog();
Animal& a2 = Dog();


Animal& a3 = max(a1, a2);

but this will not:

Animal& b1 = Dog();
Animal& b2 = Cat();
Animal& b3 = max(a1, a2);

The compiler is expected to track the value assigned to b2. It is a
static analysis and the compiler is pessimistic. For example:

Animal& c1 = Dog();
Animal& c2 = condition() ? Dog() : Cat();
Animal& c3 = max(a1, a2);

will not compile because c2 *might* be a cat and the compiler isn't
expected to statically predict what condition() will return (cf the
Halting Problem). Even though the classes violate the LSP, many uses are
safe, can be proven safe, and can be proven safe by a mechanical tool
(rather than a human).

The downside is that it may require a non-local analysis, if, for
example, c2 is an instance variable and the assignment and max() call are
in different functions. Thus a reasonable-looking change to one class
might provoke a compile-time error in a distant other. Worse, two classes
which worked fine and compiled in isolation might fail when brought
together; you lose composability. The incompatibility is not detected
until link time. This makes it harder to write a dynamic linker, and if
you do the end-user may be the first person to discover the type-error.

All of this goes to show that we can't break the LSP with impunity. It is
still earlier detection of bugs than a run-time check would give.

In C++ we have to use a run-time check. So Eiffel is indeed safer, but I
am not sure it is worth the complication.

Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."

Martin Fabian

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
Greg wrote:
>
<snip>

> // Assume Dog and Giraffe are descendents of Animal, and that
> // it should be an error to compare dogs to giraffes.
> //
> Animal& a1 = Dog;
> Animal& a2 = Giraffe;

> Animal& a3 = max (a1, a2); // no error detected.
>
Are you really sayin that (pardon my French (i.e. Eiffel :-)

class Animal inherit COMPARABLE
// blah blah blah

class Dog inherit Animal
// blah blah blah

class Giraffe inherit Animal
// blah blah blah

with
a: Animal;
d: Dog;
g: Giraffe;
create d;
create g;

really will fail on calling

a:= max(d,g);

If so, this seems a very strange variant of inheritance to me. If animal
implements COMPARABLE (which it does if it has an operator< in c++, and
if it inherits from COMPARABLE in Eiffel) then all of its descendants
will also implement COMPARABLE. Anything other than that seems totally
counterintuitive. There can be no error comparing dogs with giraffes,
not in c++ nor in eiffel.

Or am I missing something here?

--
Martin Fabian http://www.s2.chalmers.se/~fabian/
--
Ask enough experts. Eventually you'll get the answer you want.

/* Remove NOSPAM from reply-to address to mail me */

John Panzer

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
Greg wrote:

> In article <t08squp...@corp.supernews.com>,
> "Alexei Lebedev" <ale...@metastream.com> wrote:
> > Your challenge is incorrect, because if both Dog and Giraffe are
> publicly
> > derived from Animal,
> > and Animal has a comparison operator, then both Dog and Giraffe
> implement
> > the Animal interface. So it is valid to compare them as Animals.


>
> Alexei, if I'm in error in my design, then the error is originally
> Stepanov's. I refer you to the Dr Dobbs interview and Stepanov's
> discussion of mating animals.
>

> I said nothing about Animal implementing a comparison operator. It may
> only define an abstract interface to it, or may not even do that.
>

I think we're assuming that Animal advertises a comparison operator (as in
"has the comparison operator interface"). If it doesn't even have an
abstract interface, then I'd think that handing max() two Animals should be
a compile error; max() requires that its arguments be comparable, and we're
dealing with statically type checked polymorphic languages here.

If Animal does advertise a comparison operator, and the operator is
required to always implement a relation that is a partial ordering, then
any derived class that does _not_ implement such a relation is violating
substitutability. So in this case max() is a red herring; it's the <
operator that needs fixing. (I think that in Eiffel terms, the derved
classes would be violating the postcondition of the operator.)

Of course, you could define < so that it is allowed to throw a runtime
exception if two objects are not comparable. In which case max() will work
as expected.

And as another post indicated, Stepanov actually recommended against this
approach.

-John Panzer

Greg

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <m1d7g84...@edevnull.com>,

llew...@edevnull.com wrote:
>
> First, a general note about your challenge:
>
> I believe you deliberately designed it to make Eiffel appear
> superior to C++. I believe that whatever our replies to you, you
> intend to go away satisfied that Eiffel has 'won' your challenge.
>
> Perhaps it is unfair of me to judge you so harshly, but that is what
> I see.
>
> A 'challenge' of this nature is a cruel farce that does no good and
> may do much harm.

Your point is taken. I'll think about how to frame such issues so as to
more light and less heat/harm.

>
> Greg <gmc...@my-deja.com> writes:
>
> [snip]
> > I've read through the interviews, and in them are three tests that
Dr.
> > Stepanov might apply to qualify a language for GP. I'd like to
discuss
> > one of these tests, how Dr. Stepanov implemented it in one of the
> > interviews, and how the same test can be implemented in Eiffel. (In
> > another thread, I debated with a couple of people over the merits of
> > C++ vs Eiffel's generic programming facilities.)
>
> And there will be no end to this debate until the debaters admit that
>
> (a) Both languages have important advantages and disadvantages in
> this area.

I definitely agree that both languages have advantages and
disadvantages. For example, Eiffel has a much thinner set of basic data
types than C++, such as only one size of integer and no unsigned
integer support. On the other hand, Eiffel has many built-in facilities
for detecting and preventing errors that the C++ community should know
about.

> [snip]
> > And an even more simple test case:
> >
> > cout << max (100, 30) << endl;
> >
> > generates an error with my C++ compiler (VC++ 6.0, for the record.)
The
> > error? The compiler can't determine which of the two versions of max
> > should apply.
>
> *shrug* My compiler likes that test case just fine.
>

Turns out that VC++ 6.0 is a truly lousy test bench for templated code.
The standard library implementation of max() is missing and basic
functionality like this max() test case fail to work.
Looks like it's time for me to get gcc working. Sigh.

[...]


>
> I am certain I have used std::max() on classes that had *only*
> operator<() (not greater-than, not-equal etc). One of the principles
> of generic programing is to place as few restrictions on the type
> parameters as necessary - your example implies that effiel makes it
> difficult to minimize the requirements on the type parameters. My
> point is that what you describe as a disadvantage, can often be an
> advantage.
>

I certainly if one wants to exert minimal effort to implement
something, then minimal requirements are best. On the other hand, as an
implementor of a client for max(), I *have* to know that it's
implemented on operator<. Now I have a pretty tight form of coupling
between the client class and max().

By subclassing off COMPARABLE, I get all the comparison operators for
the same amount of effort (writing a function for operator<) and I'm no
longer coupled to how max() is implemented.

> >
> > Inheriting from COMPARABLE forces the implementor to write a "<"
> > function that conforms to the contract. The contract states in part
> > that asymmetry must hold for "<". In other words,
> > a < b iff b >= a
> > and
> > a < b equals not b < a
> [snip]
> > Does the Eiffel implementation have shortcomings that the C++
> > implementation does not? For one thing, Eiffel doesn't have the same
> > concept of immutibility (ie "const-ness") that C++ does. So for
better
> > or worse, there is no way to guarantee that max() won't modify it's
> > parameters. On the other hand, one could supply contracts for the
> > classes being compared, forcing them to check that they have not
been
> > modified after the comparison.
>
> Why do you bring up this issue as a deficiency of Eiffel if it is so
> easily solved by features Eiffel already has? C++ has an explicit
> feature for enforcing immutability. In Eiffel, you build it with a
> contract and a trivial amount of code. Eiffel has explicit
> features for contracts.

The C++ solution is a compile-time check with no run-time penalty. The
Eiffel check has to happen at run-time and could be very expensive to
perform for large, complex classes.

> In C++, you build them with trivial amounts
> of templated code. In both cases poorly educated programmers insist
> on seeing language defeciencies, while good programmers concentrate
> on learning how to use the features the languagues already
> provide. Simply because language X does not provide an explicit
> unambigous keyword for feature Y does not mean that doing Y in
> language X is difficult or impossible.
>

Interesting point. One of my frustrations with C++ templates is that it
adds a new syntax to the language, including a newkeyword. I think this
is reflected in how challenging it's been to write good template
libraries.

Greg

--
http://homestead.deja.com/user.gmc333/index.html


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

sirwi...@my-deja.com

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <8u7evn$g...@flatland.dimensional.com>,

j...@dimensional.com (Jim Cochrane) wrote:
> In article <3A068D47...@dresdner-bank.de>,
> James Kanze <James...@dresdner-bank.de> wrote:
> >Howard Hinnant wrote:
> >
> > ...
> >Two comments:
> > - First, I wouldn't make operator< virtual, or even a member. I'd
> > define a virtual function compare (for example), which returned
an
> > int <, == or > 0, and call that from operator< (and operator>,
and
> > operator==, ...).
> > - The virtual function shouldn't be public -- in C++, virtual
> > functions should never be public.
>
> Why should a virtual function never be public?

This is one of those "design rules" that can be taken too far. It's a
good rule, but if you don't use it you're not "evil" (or in this case,
even abnormal).

It's generally a better design to make the virtual methods protected
and have public methods that call them. For instance, the locale
facets use this idiom for calls such as money_put::put which calls the
protected virtual money_put::do_put. This allows for code called
before and after the "overidable code" that can't be modified
accidently by a poor override. This is just one reason why this design
is preferred.

--
William E. Kempf
Software Engineer, MS Windows Programmer

Louis A. Russ

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to

[snip]


>
> In Eiffel, code like this will compile:
>
> Animal& a1 = Dog();
> Animal& a2 = Dog();
> Animal& a3 = max(a1, a2);
>
> but this will not:
>
> Animal& b1 = Dog();
> Animal& b2 = Cat();
> Animal& b3 = max(a1, a2);
>
> The compiler is expected to track the value assigned to b2. It is a
> static analysis and the compiler is pessimistic. For example:
>
> Animal& c1 = Dog();
> Animal& c2 = condition() ? Dog() : Cat();
> Animal& c3 = max(a1, a2);

[snip]

That's all good for animal, but what if you have:
Inches yardInInches(36.);
Yards oneYard(1.);
Length &l1 = yardsInInches;
Length &l2 = oneYard;
Length &l3 = max(l1,l2);

Maybe that last line should be:
Length &l3 = std::max<Length>(l1,l2);
in C++.

So you'd have to work around the safety feature?

LR.

Anatoli Tubman

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Replied to comp.lang.eiffel only.

In article <8u7h8v$80l$1...@nnrp1.deja.com>,


Greg <gmc...@my-deja.com> wrote:
> No problem. Say you have a system that supports ASCII and EBCDIC-based
> strings. Both could be derived from the same base class, yet clearly
> comparing the two types directly is a serious error. At the same time,
> certain parts of the system may have no need to know the
> implementation
> of the string, and only want to manipulate it through the base type.

It is entirely unreasonable to have ASCII and EBCDIC strings
to inherit from the same string class, much like you don't
inherit array-of-integers and array-of-animals from the same array
class.

ARRAY[INTEGER] STRING[ASCII_CHARACTER]
ARRAY[ANIMAL] STRING[EBCDIC_CHARACTER]

Easy?

> -- a3 := max (a1, a2) -- Runtime error! still mixing dogs and
giraffes

Compiled with -boost, the application doesn't catch this error.

No, this is not a SmallEiffel bug. According to the Eiffel definition,
Eiffel does not have runtime errors at all. This is the language
deficiency.


--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't

James Kanze

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Greg wrote:

> A couple of interesting points between your solution and the Eiffel
> solution. In the C++ solution, the type conformity check is pushed
> down to the classes to be compared. In Eiffel, the check is in the
> max() operator instead. While it should be possible to modify the
> C++ max() so that it does the check, it doesn't appear to have been
> done this way for the library implementation of max(). So every
> class has to do the check itself, which is a little extra code
> everywhere.

It's also a lot more flexibility. And the check is normally implicit.
Since the C++ classes don't inherit from Comparable, they don't have
to support an interface operator<( Comparable const& ). The normal
case would be operator<( MyClass const& , MyClass const& ), and there
is nothing for either max or operator< to check -- you simply cannot
write it wrong. On the other hand, if you can compare different types
within a hierarchy, this is also supported. I'm not sure I understand
the Eiffel case completely, but suppose I define operator< on Person
to compare the age (I know, a stupid example). If Teacher and Student
both derive from Person, can I do something like
max(aTeacher,aStudent)? The types are different, but the objects are
definitely comparable according to my artificial comparison criterion.

> Leveraging COMPARABLE in Eiffel means that all the target classes
> need only implement operator<. If you want the full complement of
> comparison operators in C++, you have to implement them all. (I
> think another poster referred to a solution to this; when I have
> time I'll check it out. But it doesn't seem to be part of my C++
> compiler...)

It's something the standards committee added:-). It's met with mixed
acceptance. The problem is that it defines == in terms of two <.
Whereas in many cases, it is possible to determine == in less time
that for one <.

This is one case where the base class idiom probably has an
advantage. I presume that you can override the default implementation
of == (in terms of <) if you want.

> Sharing the implementation of max(), Animal, Dog, Giraffe,
> etc. means writing include files, and in the case of templates, it
> usually means distributing at least some of the implementation in
> those include files.

Theoretically, you don't need any of the implementation in headers.
Practically, today, there are still a lot of compilers which don't do
as well as CFront did ten years ago.

> Yet more code to write. Eiffel, like Java,
> provides tools for automatically extracting the interface of a
> class, so no include files (I believe the Java developers lifted
> this concept from Eiffel in the first place.)

I don't know where the Java people got the idea of mixing the contract
specification and the implementation, but it is certainly one of the
things that makes Java a pain.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

Dietmar Kuehl

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Hi,
James Kanze (James...@dresdner-bank.de) wrote:
: Dietmar Kuehl wrote:
: > It is actually

: > equally important, that the arguments are not required to be derived
: > from some specific class/interface/however you call it, like eg. the
: > "COMPARABLE": They are only required to conform to a certain abstract
: > concept, for 'max()' is the concept of being "less than comparable".

: Why is it important not to give this concept a name, and require that
: the class state explicitly that it implements it.

There would be no problem if the concepts were merely tags which are
added to the class' list of bases (except for built-in types; it might
be possible to add this to the core language that eg. 'int' seems to
derive from 'copy_constructible', 'copy_assignable', etc.). Going
beyond just tags, eg. adding any declaration to the base class which is
more than just some form of tag becomes to restricted: There is no
point in restricting the interface in any way. Since we where stuck
with the "comparable" property, here is an example for this: Assume
that we want to make sure that classes implementing "comparable" are
indeed implementing a comparison operator. Using C++ syntax this would
look like this:

struct comparable {
bool operator< (comparable const&) const = 0;
};

This has two problems:

- The obvious one is that the overriding function gets a "comparable"
as second argument which dwarfs static type checking: It would be
possible to pass any comparable object not just one of the same
type. I'm not sure whether the given Eiffel construct prevents this
but in C++ additional syntax would be required to guarantee at
compile time that only identical types can be compared.

- The not so obvious problem is the return type: Why I'm I forced to
return a Boolean? For my high precision numbers I have a tri state
return:

enum compare_result {
failed = 0, // not true
epsilon_difference, // true but a rather close call (*)
significant_difference, // true witout any doubt
huge_difference // several orders of magnitude different
};
// (*) difference in the order of computing precision only

For some applications it might be acceptable to consider this to be
just two states (and for those the use of 'max()' should definitely
be an option) but for others it might be crucial to deal eg. with the
minor difference (eg. by doing another compare the other way around
to determine whether the numbers might actually be considered to be
identical).

That is, defining anything about the interface can swiftly become a
limiting factor. ... and something as simple as a "comparable" only
shows the tip of the problem! You definitely don't want to create
artificial constraints.

: Remember the problems we have (and to a degree still have) because


: auto_ptr is not copy assignable, although it implements the operator=
: for other purposes. If we had some sort of abstract base class that
: specified "copy assignable", the problem would never have arisen;
: auto_ptr wouldn't have derived from this class.

: > In C++ the following works:

: > struct Dog {
: > void barf() const { std::cout << "barf!\n"; }
: > bool operator< (Dog const&) const { return true; }
: > };

: > max(Dog(), Dog()).barf();

: > For generic programming it is crucial that this works, in particular
: > it is necessary that the return type of 'max()' is of type 'Dog'.

: No. Generic programming works with dynamic type checking as well.

It does not. Here is an example (to really run it, some more syntax is
necessary):

int numbers() { static int rc = 0; return rc++; }

std::vector<int> array(10);
std::generate_n(array.begin(), 10, numbers);

std::sort(std::find(array.begin(), array.end(), 3),
std::find(array.begin(), array.end(), 7),
std::greater<int>());

This works in C++ but does not work if dynimic typing is used, at least
not with the same guarantees: In C++ we get a compile time error, if we
replace "vector" by "list". With dynamic typing we get a run time error
assuming the C++ constraints for "sort()" do not change (ie. the
arguments have to be random access iterators). However, it is more than
just the compile time error: Although not necessarily in this example
but with others we also get an appropriate algorithm selected for the
arguments - at compile time.

You can program generically using dynamic polymorphism but not in the
in the sense of Generic Programming. You can try to mimick the behavior
necessary for Generic Programming using dynamic polymorphism but it
does not really work that well. And in any case, you loose one of the
important properties of generic programming: The code is no longer as
fast as code written specifically for a particular type. Although this
looks like a minor nit, it is actually not: If you loose just, say, 5%
for each application of a generic algorithm, this is nothing, isn't
it? The problem becomes visible if you are using an algorithm to
implement something used within an algorithm which in turn is used
within an algorithm... The 5% loss becomes multiplied pretty quickly.
Too quickly, at least, to be acceptable in areas where algorithms often
use other algorithms to solve subproblems (eg. the area of graph
algorithms is a good example for this).

: It's a lot more dangerous, but it works.

: > If it
: > does work for Eiffel, Eiffel might be usable to do generic
: > programming. However, this is not the only restriction. It is also
: > necessary that certain operation return types depending on the exact
: > type of the function arguments, not just some type know to a base
: > class.

: But it isn't necessary for this dependance to be evaluated at compile
: time.

It is. See above.

: > For example, the function accessing an element identified by an


: > iterator (in C++ 'operator*()') has to return a type depending on the
: > underlying sequence: If the iterator is eg. moving over a sequence of
: > 'int's, the result of accessing an element has to be a 'int', too.

: No. It has to return a type that can be used as an int. Generic
: programming works in Smalltalk, for example.

No, it does not, at least not in the sense the term "Generic Programming"
is used in Matt Austern's book or by Alexander Stepanov. You can program
generically in Smalltalk but this does not mean that you can do Generic
Programming with Smalltalk. The compile time part of the algorithms is
indeed crucial for the successful application of this paradigm.

: > If


: > the underlying sequence has some other type, the function accessing the
: > element has to return an object (or reference to such an object) of the
: > corresponding type.

: It will, at least for the dynamic type. The correct static type is
: necessary for compile time type checking, but generic programming will
: work even in dynamically typed languages.

See above.

: I think it was once Stroustrup himself who said that Smalltalk was a


: better Smalltalk than C++ was.

Excellent quote.


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

bran...@cix.compulink.co.uk

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
lr...@superlink.net (Louis A. Russ) wrote (abridged):

> That's all good for animal, but what if you have:
> Inches yardInInches(36.);
> Yards oneYard(1.);
> Length &l1 = yardsInInches;
> Length &l2 = oneYard;
> Length &l3 = max(l1,l2);
>
> Maybe that last line should be:
> Length &l3 = std::max<Length>(l1,l2);
> in C++.
>
> So you'd have to work around the safety feature?

I imagine you would adopt the same solution as in C++, of making all
Lengths comparable with each other via an operator<() in the base class.
The subclasses would respect the Liskov Substitution Principle, at least
as far as comparison was concerned. The Eiffel rule only comes into play
when the LSP is broken.

In this regard Eiffel is a superset of C++, so in the worst case you can
always adopt the C++ solution.

Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

bl...@blah.blah

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
On 7 Nov 2000 13:13:50 -0500, Greg <gmc...@my-deja.com> wrote:

>No problem. Say you have a system that supports ASCII and EBCDIC-based
>strings. Both could be derived from the same base class, yet clearly
>comparing the two types directly is a serious error. At the same time,

Not at all. It's perfectly ok to compare ascii with ebcdic if your
comparison algorithm takes into account the differences between them to
compare their abstract contents instead of their physical representation.

Good programming languages let you use code such as
myAsciiString < myEbcdicString
which would evaluate to true or false.

saul tamari

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Hi

Can you please explain what you mean ? Why are the contract
specification & the implementation mixed ? Why is it different from C++
?


In article <3A092301...@dresdner-bank.de>,
James Kanze <James...@dresdner-bank.de> wrote:
>...


> I don't know where the Java people got the idea of mixing the
contract
> specification and the implementation, but it is certainly one of the
> things that makes Java a pain.

Thanks
Saul Tamari

Tim Van Holder

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
In a burst of inspiration, "Greg" <gmc...@my-deja.com> wrote this on
<8u6og2$gto$1...@nnrp1.deja.com>:

> Howard: thanks for posting code instead of flames :-). I got your
> example to compile. I think Stepanov originally included the non-const
> version for completeness; perhaps it is needed to cover the case where
> operator< needs to modify the target class in some way (computing and
> caching an intermediate result for comparison?) Regardless, I tend to
> agree that in general the non-const version should not be needed (and
> indeed isn't part of the standard library).

Even classes caching results in such a way would need to accept the const
version. Since this cached value would normally change even for a const
object, such a value should be a mutable in the first place.

--
Tim Van Holder - Falcon Software NV
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Fight spam - go to http://www.politik-digital.de/spam/

James Kanze

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Greg wrote:

> I think Stepanov originally included the non-const version for
> completeness; perhaps it is needed to cover the case where operator<
> needs to modify the target class in some way (computing and caching
> an intermediate result for comparison?)

I think the reason is to support things like:

max( a , b ) = 0 ;

IMHO, this is carrying things too far. I don't think I'd like to
maintain code which made heavy use of such idioms.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

Dietmar Kuehl

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Hi,
James Kanze (James...@dresdner-bank.de) wrote:
: Dietmar Kuehl wrote:
: >: > template <class T>

: >: > inline const T& max(const T& x, const T& y) {
: >: > return x < y ? y : x;
: >: > }

: >: Not only will those two cause ambiguity errors,

: > Can you please spell out any situation where these two definitions might
: > provide an ambiguity error? Just one! (hint: these functions are under
: > no circumstances ambiguous). ... and don't try such a lame approach as
: > "my [V]C++ compiler tells me that these two function are ambiguous"!
: > Yes, there are broken compilers out there.

: Well, there is an obvious ambiguity problem, but you don't need the
: two functions to show it:

: max( 0L , 1.5 ) ;

This is an abiguity because you are trying to compare apples (a long) to
oranges (a double). You can resolve the ambiguity in a lots of ways, eg.
by telling the compiler that the apple and the orange should be compared
as if they were both apples:

max<long>(0L, 1.5);

You can even go as far as comparing them according to something even
different from both:

max<int>(0L, 1.5);

You just have to tell the compiler which rules to use. That you cannot
compare apples to oranges is good although I admit it is in some sense
bad: Normally, the compiler does not complain about apples being
compared to oranges. That is, the rules have changed.

: > As long as you use the


: > result only in the same statement this is no problem, however:
: > Temporaries are destructed at the end of the enclosing full
: > expression.

: >: In other words, there should also be a THIRD max:

: >: template <class T>
: >: inline const T max(const T& x, const T& y) {
: >: return x < y ? y : x; // Returns a copy of x or y, not a
: >: reference.
: >: }

: > No, you don't need a third a version. The two versions given above
: > are just fine.

: But in practice, you only want the const version, I would think.

No, you want both (this point was missed by others in this thread,
too): If you want to change the result of the 'max()' function, you
want to get a non-const reference. The const version is necessary
because you want to be able to apply the 'max()' function to const
arguments, too. In any case, 'max()' itself is a const function and
it might useful be able to declare this explicitly:

template <class T>
T const& max(T const&, T const&) const; // not C++!

The last const would indicate that the function neither mutates one of
the arguments nor anything else.

: And


: in practice, I'd go for the return by value (and not by reference) --
: it's safer with regards to the lifetime of temporary issue, and I
: think that the compiler should always be able to optimize the
: additional copies out.

The problem with this is that it would be impossible to apply the
function to base classes:

struct Animal {
virtual ~Animal() = 0;
bool operator< (Animal const&) const;
};
struct Dog: Animal {};
struct Giraffe: Animal {};

Animal& a1 = Dog();
Animal& a2 = Giraffe();


Animal& a3 = max(a1, a2);

What static type should a3 have if a value is returned? A different
setting is the one mentioned above, where you want to change the
maximum value returned from the 'max()' function, ie. if object
identity is some concern: the value would be worth nothing. It is not
just an optimization. It is also added flexibility.


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Paul Cohen

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to

James Kanze wrote:

> Greg wrote:
>
> > Yet more code to write. Eiffel, like Java,
> > provides tools for automatically extracting the interface of a
> > class, so no include files (I believe the Java developers lifted
> > this concept from Eiffel in the first place.)
>

> I don't know where the Java people got the idea of mixing the contract
> specification and the implementation, but it is certainly one of the
> things that makes Java a pain.
>

I am not sure I understand what you mean by "the contract specification",
but I suspect you mean the name and signature of a given function/method.

I am also unsure what you mean by "mixing" the contract specification
and the implementation, but I suspect you mean the fact that Java does
not separate between the declaration of a function and the definition of
the same function.

Could you clarify?

/Paul

Artur Biesiadowski

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
sirwi...@my-deja.com wrote:

> It's generally a better design to make the virtual methods protected
> and have public methods that call them. For instance, the locale
> facets use this idiom for calls such as money_put::put which calls the
> protected virtual money_put::do_put. This allows for code called
> before and after the "overidable code" that can't be modified
> accidently by a poor override. This is just one reason why this design
> is preferred.

Funny, I have though exactly otherwise. I would allow public method to
be overidden, possibly adding some pre and post actions and keep method
doing actual work final/frozen. After all it does access private
internals of class - there could be no way to override it in reasonable
way.

Example of what I mean is java HashMap. It has public put method which
in turn calls private internalPut method which does actual work. Now you
can override put, do some actions and call super.put() which will in
turn call internalPut. Seems to be irrevelant, BUT there is also such
thing as rehash in this implementation. When hashtable got's too big it
is resized. Implementation reputs all values into new table. In early
versions of java it used put to achive this, which really messed with
user code (it seemed that some unknown entity was putting same values
over and over). Now it just uses internalPut which cannot be overriden,
but contains actual code for computing hashes/creating buckets etc.

InternalPut even could not be overridden - it needs access to
implementation specific private variables, which are not available from
subclasses.

In this case it seems to be that it should be a public interface method
which can be overridden, not the internal routine. But maybe in other
cases it is better to do as you suggest.

Artur

Greg

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
In article <8u22er$chi$1...@slb7.atl.mindspring.net>,
"Ken Bloom" <ken...@bigfoot.com> wrote:
[...]

> You can do that in C++, but it isn't required like in eiffel. boost's
> operators.hpp does a really good job of ensuring proper semantics of
all the
> operators.

Ken, where can I get more information on Boost?

Greg

--
http://homestead.deja.com/user.gmc333/index.html


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dietmar Kuehl

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Hi,
Greg (gmc...@my-deja.com) wrote:
: Dietmar: I think the key quote from the interview is:

: "Then you derive giraffe from animal and, of course, it has a function
: mate where giraffe mates with animal and returns an animal. It's
: definitely not what you want."

My point is just this: Your claim that breaking LSP for the comparison
function was suggested by Stepanov. It was not. He mentioned this
design but immediately said that this not what is to be done.

That is, you proposed something I'm considering to be broken and you
are going on to derive from this that C++ is worse than Eiffel. I
simply do not follow this logic because C++ works just fine if the
design is corrected. ... and, as I have pointed out, it remains that
way even with your broken design if you use the tools available.

: He does go on to say that the solution is to use templates. Templates


: are the generic programming mechanism in C++, correct?

Sure. That's his point: The task at hand is best solved using templates
because they don't can be used to implement algorithms independent from
details of the interfaces as long as the notation remains the same.

: OK, what I'm looking for here is a solution that allows inheritance,


: and allows giraffes to mate with giraffes, even if the objects are
: bound to declarations of animals. In other words, in my example of max
: () the compiler and run-time checks should guarantee that a giraffe is
: never compared with a non-giraffe.

... and I have even shown you how to do this: You can use 'typeid()' to
compare the types where necessary. Here are two different approaches
to solve this problem:

template <class T>
T const& max(T const& arg1, T const& arg2) {
if (typeid(arg1) != typeid(arg2))
throw std::runtime_exception("argument types mismatch");
return arg1 < arg2? arg2: arg1;
}

This function has two restrictions:
- It is not overloaded for non-const references. This artificial. BTW,
let me point out that the overload for non-const references is not
at all related to the 'max()' function changing the arguments. The
point is that the return value can be changed through the non-const
reference, something like this:

max(competitor1, competitor2).setTitle("Champion");

You cannot do this if you are returning a const reference.

- This implementation of the 'max()' function works only for
polymorphic types, ie. types with at least one virtual function.
This restriction has its root in the C++ approach to overhead
reduction: If you don't use polymorphic types, there should be
no overhead, ie. the objects should not suffer from carrying around
an unused pointer.

Now for the second example which is basically what I have already
posted but extended to prevent some of your other criticism as well:

struct comparable {
virtual ~comparable() {}

bool operator< (comparable const& c) const {
return compare(c);
}
bool operator> (comparable const& c) const {
return !compare(c);
}
bool operator<= (comparable const& c) const {
return !c.compare(*this);
}
bool operator>= (comparable const& c) const {
return c.compare(*this);
}
bool operator== (comparable const& c) const {
return !(*this < c || c < *this)
}
bool operator!= (comparable const& c) const {
return *this < c || c < *this;
}

private:
bool compare(comparable const& c) const {
if (typeid(*this) != typeid(c))
throw std::runtime_exception("argument types mismatch");
return do_compare(c);
}
virtual do_less_than_compare(comparable const& c) const = 0;
};

The 'max()' function stays unchanged. You just derive your "Animal" (or
in general your base class you want to be able to be comparable) from
this class and implement the function "do_less_than_compare()".

- You obviously get the all comparison operations by just defining
the "do_less_than_compare()" function. Of course, a real
implementation of this class might provide all six comparison
functions being implemented in terms of each others and the user
could choose which one to override (except, of course, that equal and
not equal are obviously insufficient) or to provide customized
implementations for more than just one of these functions. The above
implementation is just a sketch of what is to be done.

- You don't have to implement the type check for each class. Neither do
you have to implement the type check for each algorithm! It is in the
base class, implemented for all types choosing to derive from this
particular class. If you don't want the type check there, you can of
course use a better suited base class.

That is, if you accept the restriction that your class has to derive
from a common base class (this restriction is present in the Eiffel
implementation and should thus also be acceptable for the C++
implementation) you can get what you want - and you don' thave to
rewrite the 'max()' function at all! It just stays the same and the
error detection is moved where it belongs: Into the class providing
the comparison operator.

: The Eiffel solution gives me this automatically and easily. The C++


: solution (which I agree can't be accomplished without templates) takes
: a conscious effort on the part of the programmer to provide these
: checks and detect the error.

So basically your complaint about C++ comes down to the missing
"comparable" class? In some sense, I agree that the standard C++
library is definitely not as complete as it could be. In another sense
I think that the standard C++ library is actually quite big.

Since there are multiple design choices to even a class that simple
(Should it do a compile time check? Which function should be
overriden? What is the correct return type of the 'operator<()'
function? etc.) I think it is a good choice to leave it out but make
the algorithms using the "less than comparable" concept working as long
as my class provides a less than operator (with the correct semantics,
of course): I can use a base class tailored to my preferences or even
go without a base class at all if this provides the correct semantics
for me.

Concerning the conscious effort stuff: Sure, some thought is needed.
This is also the case for the Eiffel implementation. At least in the
C++ case the programmer will, however, like to the right thing: If he
just tries to use 'max()', he will find that he needs an 'operator<()'
on the class where he want to use. When he tries to implement this
operator for a base class, he will notice that this can't work (unless,
of course, all objects of the base class can be comparable eg.
according to a common property) and make the function 'virtual'. Then,
if the user implements the function in the derive class, he is actually
likely to come up with an implementation like this:

bool Dog::less_than_compare(comparable const& c) const {
Dog const& d = dynamic_cast<Dog const&>(c);
return weight() < d.weight();
}

... and forget about the check. This does not matter, howerver: The
dynamic_cast() will throw a 'std::bad_cast' exception, if 'c' is not a
'Dog' or a class derived thereof (which, BTW, provides yet another
implementation approach giving a third alternative to the two given
above, using slightly different semantics). Actually, a pretty feasible
thing to do, isn't it? Of course, this does not put the check into the
'max()' function but somewhere where I still think it belongs, namely
into the class defining the comparison.


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dietmar Kuehl

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Hi,
James Kanze (James...@dresdner-bank.de) wrote:
: Howard Hinnant wrote:

: > In article <8u3pqs$8ia$1...@nnrp1.deja.com>, Greg <gmc...@my-deja.com>
: wrote:

: > | My point, in part, is how difficult it is in C++ to implement a feature
: > | as simple as max() and have it work correctly (including error
: > | detection) across many circumstances.

: > Setting aside for the moment questions of design, obtaining the
: > behavior you desire in C++ does not seem like that much work to me.

: > First off, pair your max template down to just:

: > template <class T>


: > inline
: > const T&
: > max(const T& x, const T& y)
: > {
: > return x < y ? y : x;
: > }

: Agreed. It is a design error to allow < to modify its operands,
: although neither C++, nor apparently Eiffel, can prevent it. I see no
: need for max to support design errors, however.

The reason to return a non-const reference is to allow modifications to
the result of 'max()', not allowing 'max()' to change the arguments.
Although the standard C++ library only provides the const version, I
think it should provide both versions.

Dietmar Kuehl

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Hi,
James Kanze (James...@dresdner-bank.de) wrote:
: Dietmar Kuehl wrote:

: > Greg (gmc...@my-deja.com) wrote:
: >: Alexei, if I'm in error in my design, then the error is originally


: >: Stepanov's. I refer you to the Dr Dobbs interview and Stepanov's
: >: discussion of mating animals.

: > Dump question: Have you at least glanced at this paragraph? Stepanov


: > explicitly says this is *NOT* what is to be done! (the exact words are

: > "It's definitely not what you want." but the fact that this is not the


: > road to a solution is clear from the beginning of the paragraph - maybe
: > it is not as obvious if you only pick some words from the paragraph).
: > ... and the paragraph immediately following the paragraph about mating
: > animals gives a solution how to address the very problem: Use template,
: > not inheritance.

: But this is only a sometimes solution. It doesn't work if you need
: the run-time dispatch.

I was just argueing against the the implication that it was Stepanov
who suggested the break of LSP! If you need to break LSP, you can do so
and you can fairly easily do the necessary run-time check in C++, too.
Actually, since the base class will probably use a virtual function
which in turn is almost certainly implemented with a 'dynamic_cast()',
the comparison is almost certainly checked anyway (however, the error
might be better reported than throwing a "std::bad_cast"...!). This
just didn't occur to me when I wrote my previous responses...

: Even with inheritance, however, I don't see the problem as
: particularly difficult in C++.

Agreed: Actually, the C++ solution puts the check where it belongs in
the first place, namely into the comparison rather than the 'max()'
function. I don't know whether it is indeed a typical Eiffel approach
to put the check into the algorithm...

Dietmar Kuehl

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
Hi,
Greg (gmc...@my-deja.com) wrote:
: In article <8u22er$chi$1...@slb7.atl.mindspring.net>,

: "Ken Bloom" <ken...@bigfoot.com> wrote:
: [...]

: > You can do that in C++, but it isn't required like in eiffel. boost's
: > operators.hpp does a really good job of ensuring proper semantics of
: all the
: > operators.

: Ken, where can I get more information on Boost?

Have a look at <http://www.boost.org/>.

Greg

unread,
Nov 8, 2000, 11:05:38 PM11/8/00
to
In article <8ub6mr$6nu$1...@nnrp1.deja.com>,
Anatoli Tubman <ana...@my-deja.com> wrote:
> Replied to comp.lang.eiffel only.
>
[...]

>
> > -- a3 := max (a1, a2) -- Runtime error! still mixing dogs and
> giraffes
>
> Compiled with -boost, the application doesn't catch this error.
>
> No, this is not a SmallEiffel bug. According to the Eiffel definition,
> Eiffel does not have runtime errors at all. This is the language
> deficiency.

I suppose you could see it that way. On the other hand, without -boost
the error _is_ detected. So once a program is tested, the runtime
checks can be optimized out.

On the other hand the dynamic cast used in the C++ solutions I've seen
_can't_ be optimized out.

Anatoli Tubman

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <8ud7qf$ua9$1...@nnrp1.deja.com>,

Greg <gmc...@my-deja.com> wrote:
> In article <8ub6mr$6nu$1...@nnrp1.deja.com>,
> Anatoli Tubman <ana...@my-deja.com> wrote:
> > Replied to comp.lang.eiffel only.
> >
> [...]
> >
> > > -- a3 := max (a1, a2) -- Runtime error! still mixing dogs and
> > giraffes
> >
> > Compiled with -boost, the application doesn't catch this error.
> >
> > No, this is not a SmallEiffel bug. According to the Eiffel
> >definition,
> > Eiffel does not have runtime errors at all. This is the language
> > deficiency.
>
> I suppose you could see it that way. On the other hand, without -boost
> the error _is_ detected. So once a program is tested, the runtime
> checks can be optimized out.

But there's absolutely no guarantee whasoever, anywhere, that your
next compiler, or the next version of your current compiler,
will do this for you.

Better rely on your own code, and a good debugger.

Besides, testing can prove presence of an error, but not absence of
one.

> On the other hand the dynamic cast used in the C++ solutions I've seen
> _can't_ be optimized out.

Replace dynamic_cast with static_cast once a program is tested.

Better yet, utilize *good design* that conforms to LSP and live
happily free of type errors ever after.

--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't

James Kanze

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
Artur Biesiadowski wrote:

> sirwi...@my-deja.com wrote:

The example is interesting, but fails on two points (one arguable):

- No one is claiming that *all* private functions should be
virtual. The claim is the reverse, that all virtual functions
should be private or protected. One does not entail the other.

- This "feature" of HashMap is arguably poor design. Normally,
HashMap implements a map by means of a hash table. If my design
requires something else, then I implement something else. I may
use HashMap in the implementation of that something else, but this
should be a hasA relationship, not a isA. If HashMap is designed
for customization (not the case, I think), modern design strategy
would be to use delegation -- whatever is customizable is
delegated to a separate interface; the HashMap instantiates a
default instance, which can be replaced.

The general rule is that you don't derive from concrete classes.

> InternalPut even could not be overridden - it needs access to
> implementation specific private variables, which are not available
> from subclasses.

> In this case it seems to be that it should be a public interface
> method which can be overridden, not the internal routine. But maybe
> in other cases it is better to do as you suggest.

Well, HashMap is what I would consider a very concrete class; in Java
parlance, it should be final (cannot be derived from). So the problem
doesn't occur. But supposing it does: the public interface should not
be virtual. The function put has a very definite contract: after put,
the hash table contains an element with the given key. (It may have
contained an element with the given key before, or not. We neither
know nor care.) If you are going to allow derivation, you must be
able to verify that the derived classes maintain the contract. This
would mean the following implementation put:

template< typename Key , typename Value >
void HashMap::put( Key key , Value value )
{
doPut( key , value ) ;
assert( contains( key ) && get( key ) == value ) ;
}

The protected (or private) function doPut would contain the actual
implementation.

This argumentation is actually more reasonably applicable to the
interface Map. In which case, doPut would be a pure virtual function,
with a different implementation for HashMap and for TreeMap. But the
post condition must hold in both cases.

And I'll skip the philosophizing about whether HashMap and TreeMap
should or should not derive from Map, given that their derivation
violates the LSP. (They both have stricter preconditions: HashMap
that the key type support hash codes, and TreeMap that it be
orderable.)

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
saul tamari wrote:

> Can you please explain what you mean ? Why are the contract
> specification & the implementation mixed ? Why is it different from
> C++ ?

C++ allows (but doesn't require) putting the contract specification
(the class definition, if you prefer) in a separate file from the
implementation. If you make systematic use of the compilation
firewall idiom, you can easily arrange that the header file contains
the contract, all of the contract, and nothing but the contract.

In Java, the entire class must be defined in a single file, despite
the fact that on large projects, it is normal for different people to
be responsible for the interface specification (the class definition,
in C++ terms) and the implementation (the member function definition,
and the definition of the implementation class if the compilation
firewall idiom used, in C++ terms).

James Kanze

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
Paul Cohen wrote:

> James Kanze wrote:

> > Greg wrote:

> > > Yet more code to write. Eiffel, like Java,
> > > provides tools for automatically extracting the interface of a
> > > class, so no include files (I believe the Java developers lifted
> > > this concept from Eiffel in the first place.)

> > I don't know where the Java people got the idea of mixing the contract
> > specification and the implementation, but it is certainly one of the
> > things that makes Java a pain.

> I am not sure I understand what you mean by "the contract
> specification", but I suspect you mean the name and signature of a
> given function/method.

Plus, of course, documentation and/or enforcement of the pre- and
post-conditions, etc. Basically, everything that you would want to
end up in the javadoc.

> I am also unsure what you mean by "mixing" the contract
> specification and the implementation, but I suspect you mean the
> fact that Java does not separate between the declaration of a

> function and the definition of the same function.

Correct. On large C++ projects, it is usual for different people to
be responsible for the header file and the implementation files. (In
such cases, of course, the compilation firewall idiom is de rigor, so
the header file contains *only* information concerning the external
interface.)

> Could you clarify?

What more is there to say. One of the recognized weaknesses of C++ is
that the separation isn't great enough -- the class definition must
also contain information that doesn't concern the class users, like
the declaration of private data members. Over time, we've developed
idioms to work around these limitations, like the compilation firewall
idiom. Given that this partial lack of separation is recognized as a
weakness in C++, I find it surprising that Java would intentionally
make the situation much worse.

James Kanze

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
Dietmar Kuehl wrote:

>: But this is only a sometimes solution. It doesn't work if you need
>: the run-time dispatch.

> I was just argueing against the the implication that it was Stepanov
> who suggested the break of LSP! If you need to break LSP, you can do
> so and you can fairly easily do the necessary run-time check in C++,
> too.

Without knowing the actual application, I don't think you can say
outright that it violates LSP. LSP doesn't forbid preconditions on a
function; presumably, those preconditions can include constraints on
the actual types involved. That doesn't mean that I'm not suspicious
with something as general as "both must have exactly the same type",
but I could easily imagine some sort of type compatibility as part of
a generalized precondition expression.

Of course, we both agree that predicates over the type system are best
handled by the template mechanism, provided the lack of dynamic
dispatch is acceptable (as it usually will be in such cases). The
condition that "both must be of the same type" sounds remarkably like
the answer Matt Austin and Christopher Eltschka gave me when I asked
about the formal reasoning behind choosing templates over inheritance;
only templates can statically enforce predicates over the type system.

And of course, if for some reason dynamic dispatch is needed, then C++
supports the dynamic verification of such predicates as well.

> Actually, since the base class will probably use a virtual function
> which in turn is almost certainly implemented with a
> 'dynamic_cast()', the comparison is almost certainly checked anyway
> (however, the error might be better reported than throwing a
> "std::bad_cast"...!). This just didn't occur to me when I wrote my
> previous responses...

In general, I don't like systems which "throw away information needed
later"; in this case, if at one point, it is known that the variable
"a" has type Giraffe, and it is necessary at a later point to know
this as well, I would prefer to maintain it as a Giraffe, and not as
an Animal. In practice, there are cases where this isn't pratical,
but I find it a good design goal anyway.

IMHO, there is an open question here, however. Can I really be
certain that type identity is, and will always be, an incontournable
precondition. It certainly isn't an incontournable precondition for
max, and it definitly should not be verified in max. (The classical
example for where type identity is considered necessary is the
function Animal::mate. Except that Horse::mate( Donkey ) has to work,
or we'll never have any mules.)

A more interesting point concerning the example is that to correctly
implement the general case, you need double dispatch. C++ doesn't
have this, and from the way the example was presented and worded, I
suspect that neither does Eiffel. Common Lisp does, and this example
would probably be a very good one if one were trying to prove common
Lisp superior to C++ or Eiffel. (When you want to prove something,
you choose your examples carefully.)

An interesting comparison might involve implementing the double
dispatch in both C++ and Eiffel. I know how to do it in C++, although
the results are closed -- any new classes in the hierarchy require a
modification in the base class and/or a general interface. It would
be interesting to see how to do it in Eiffel; it is typically a
weakness of statically typed languages, and a good test of their
flexibility. (In a language like Common Lisp, of course, you would
just define the necessary functions:

mate( Giraffe , Giraffe ) ;
mate( Horse , Horse ) ;
mate( Donkey , Donkey ) ;
mate( Horse , Donkey ) ;
// ...

The system would choose the correct one at run time, according to the
dynamic type of both arguments, or raise an exception if there were no
corresponding function. The functionality has a non-negligible
run-time, and I don't know how you could fit it into the C++ model.)

James Kanze

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
Dietmar Kuehl wrote:
>: Well, there is an obvious ambiguity problem, but you don't need the
>: two functions to show it:

>: max( 0L , 1.5 ) ;

> This is an abiguity because you are trying to compare apples (a
> long) to oranges (a double).

Well, from my point of view, I'm trying to compare two numeric values.

> You can resolve the ambiguity in a lots of ways, eg.
> by telling the compiler that the apple and the orange should be compared
> as if they were both apples:

> max<long>(0L, 1.5);

> You can even go as far as comparing them according to something even
> different from both:

> max<int>(0L, 1.5);

> You just have to tell the compiler which rules to use. That you
> cannot compare apples to oranges is good although I admit it is in
> some sense bad: Normally, the compiler does not complain about
> apples being compared to oranges. That is, the rules have changed.

There is a fundamental problem in the C/C++ type conversion rules. In
most other languages, the numeric types are hierarchized. If implicit
conversion is allowed at all (and if you support several different
sizes of integers or floating points, I think it must be), then it
only goes from narrow to wide, not the reverse. If the conversion
would only work in one direction, there would be no problem making max
work exactly like +.

Such is the price you pay for C compatibility. There was a proposal
to deprecate the implicit narrowing conversions. Personally, I can't
see the slightest reason against it, but it failed to pass. Had it
passed, of course, the rules involving function overloading when
templates are involved would still have to be adopted to take it into
account.

>: > As long as you use the
>: > result only in the same statement this is no problem, however:
>: > Temporaries are destructed at the end of the enclosing full
>: > expression.

>: >: In other words, there should also be a THIRD max:

>: >: template <class T>
>: >: inline const T max(const T& x, const T& y) {
>: >: return x < y ? y : x; // Returns a copy of x or y, not a
>: >: reference.
>: >: }

>: > No, you don't need a third a version. The two versions given above
>: > are just fine.

>: But in practice, you only want the const version, I would think.

> No, you want both (this point was missed by others in this thread,
> too): If you want to change the result of the 'max()' function, you
> want to get a non-const reference.

But I don't want to change the result of the max() function. That is
exactly my point. The only use for changing the result of the max()
function I can think of is code obfuscation.

Max is a function. It deals with *values*. As such, it's return
value is a value, not an lvalue. It's possible to write it so that
the return value is an lvalue, but using it as an lvalue can only
confuse the reader of the code.

> The const version is necessary
> because you want to be able to apply the 'max()' function to const
> arguments, too. In any case, 'max()' itself is a const function and
> it might useful be able to declare this explicitly:

> template <class T>


> T const& max(T const&, T const&) const; // not C++!

> The last const would indicate that the function neither mutates one
> of the arguments nor anything else.

In sum, that it is a function:-). This is something that is missing
from a lot of languages. It can be increadably useful, especially to
the compiler. If the compiler knows that sin doesn't modify any
state, and that it always returns the same value for the same input
arguments, then it can optimize away the second call in
sin(x)*sin(x). In certain applications, I could imagine that that
could make a significant difference in run-time.

>: And
>: in practice, I'd go for the return by value (and not by reference) --
>: it's safer with regards to the lifetime of temporary issue, and I
>: think that the compiler should always be able to optimize the
>: additional copies out.

> The problem with this is that it would be impossible to apply the
> function to base classes:

> struct Animal {
> virtual ~Animal() = 0;
> bool operator< (Animal const&) const;
> };
> struct Dog: Animal {};
> struct Giraffe: Animal {};

> Animal& a1 = Dog();
> Animal& a2 = Giraffe();
> Animal& a3 = max(a1, a2);

Good point. The lifetime of temporaries is probably not a serious
problem in practice, since it isn't really usual to use references
with value objects anyway, except as function parameters, and the
normal lifetime of a temporary is sufficient in that case.

Of course, in my mind, max is really only relevant for values, not
objects (which is doubtlessly why I don't feel the need for the
non-const version either). And my experience is that inheritance and
values don't mix very well anyway; a type is either an OO type object
(a model object), which may contain values, but isn't a value itself,
or it is a value (like int or double). The idioms for dealing with
each are different.

Thus, the simple idea of wanting to apply a function like max (or any
other value oriented function) to an inheritance hierarchy is a danger
signal in my mind. It doesn't work. The fact that some languages
more or less force you to deal with values in terms of OO objects is a
weakness of those languages. The only language I actively know that
does this (sort of) is Java, and it provides a number of features to
make the distinction, such as final classes, so even there, I'm not
sure. (In Java, a value type can be fairly well simulated by making
the class immutable and final. Java's String is a good example of
this; I prefer it to std::string. Java's java.awt.Dimension is a good
example of where the authors didn't give the issue any thought, with
the results that classes using it -- like the entire GUI hierarchy --
almost always end up with unspecified behavior.)

Greg

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <mjsg0tcphtcbcu8nn...@4ax.com>,

bl...@blah.blah wrote:
> On 7 Nov 2000 13:13:50 -0500, Greg <gmc...@my-deja.com> wrote:
>
> >No problem. Say you have a system that supports ASCII and EBCDIC-
based
> >strings. Both could be derived from the same base class, yet clearly
> >comparing the two types directly is a serious error. At the same
time,
>
> Not at all. It's perfectly ok to compare ascii with ebcdic if your
> comparison algorithm takes into account the differences between them
to
> compare their abstract contents instead of their physical
representation.
>
> Good programming languages let you use code such as
> myAsciiString < myEbcdicString
> which would evaluate to true or false.
>
> [ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> [ about comp.lang.c++.moderated. First time posters: do this! ]
>
>

Well, we're talking C++ and Eiffel, and I've never seen this in either.
If you know of a C++ package that does this, show me.

I'm especially interested in how it handles characters that are in one
standard and not in the other.

Greg

--
http://homestead.deja.com/user.gmc333/index.html


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

bran...@cix.compulink.co.uk

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
ku...@ramsen.informatik.uni-konstanz.de (Dietmar Kuehl) wrote (abridged):

>: No. Generic programming works with dynamic type checking as well.
>
> It does not. Here is an example [...]

>
> This works in C++ but does not work if dynimic typing is used, at least
> not with the same guarantees: In C++ we get a compile time error, if we
> replace "vector" by "list". With dynamic typing we get a run time error
> assuming the C++ constraints for "sort()" do not change (ie. the
> arguments have to be random access iterators).

Well, yes, that's what dynamic typing means: some error detection is
deferred until run time. This will have no effect on correct programs.


> However, it is more than just the compile time error: Although
> not necessarily in this example but with others we also get
> an appropriate algorithm selected for the arguments

This is more to do with multiple dispatch than static typing. Admittedly
Smalltalk is a single-dispatch language, so we should really be looking
at Dylan, Cecil, CLOS for this kind of thing.

As you say, it is not necessary in this example. Nor in many others. The
single-dispatch subset of Generic Programming, supported by Smalltalk, is
quite useful.


> - at compile time.

Yes, the algorithm selection is logically done at runtime in a dynamic
multimethod language.

Later you talk about speed differences. As far as I can tell you are
talking about the costs due to using a poorer algorithm, due to not being
able to use special algorithms in special cases. This kind of speed cost
can be avoided in Dylan etc because it does let us do the
specialisations. In fact, because it is dynamic, we can use the faster
algorithm in situations where C++ would not.

There may also be a speed cost due to runtime overheads in the dispatch
mechanism. However, now we're comparing implementations not languages.
Good optimisers should produce good code even for dynamic languages,
especially if they happen to have available the kind of static info which
C++ demands.


>: No. It has to return a type that can be used as an int. Generic
>: programming works in Smalltalk, for example.
>
> No, it does not, at least not in the sense the term "Generic
> Programming" is used in Matt Austern's book or by Alexander
> Stepanov. You can program generically in Smalltalk but this does
> not mean that you can do Generic Programming with Smalltalk.
> The compile time part of the algorithms is indeed crucial for
> the successful application of this paradigm.

I agree that Smalltalk isn't quite up to it, but the problem is not
dynamic versus static typing but single versus multiple dispatch.

This is important in the context of Eiffel because Eiffel *is* statically
type checked. It even has some support for genericity. However, it lacks
multiple dispatch. Multiple dispatch is an advantage C++ has over Eiffel.

Another feature of C++'s overloading is that it is open, in the sense
that we can specialise on a type without modifying that type, post-hoc.
We can't do that in Eiffel, because in Eiffel only members may specialise
and the list of members is lexically closed by the class's definition.

We can get the effect in Smalltalk, however, because although the
specialising method must be a member, the list of members is not
lexically closed. We can add new member functions in very loose and
flexible ways. This gives Smalltalk the openness benefit, which it shares
with C++.


(By the way, I am reading this in comp.lang.c++.moderated and won't see
replies posted in comp.lang.eiffel.)

Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dylan Nicholson

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <8ucako$dnt$4...@news.BelWue.DE>,
dietma...@yahoo.com wrote:
>
<snip>

>
> The reason to return a non-const reference is to allow modifications
to
> the result of 'max()', not allowing 'max()' to change the arguments.
> Although the standard C++ library only provides the const version, I
> think it should provide both versions.
> --

Actually I hit this problem recently - I wanted to declare a function
that promised not to modify its arguments, but to return a non-const
reference to one those arguments in order to allow the caller to use
its return value as an lvalue...is there a recommended way of doing
this?

Dylan


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Wayne Pollock

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
Dietmar Kuehl wrote:
>
> The reason to return a non-const reference is to allow modifications to
> the result of 'max()', not allowing 'max()' to change the arguments.
> Although the standard C++ library only provides the const version, I
> think it should provide both versions.

>From what I have read, I thought the reason max() and others returned
a const reference is that the return value was designed to have the
semantics of a temporary. While it is sometime convenient and more
efficient to modify a temporary, a standard library can't always
tell when it is ok and when it is not. So only one version of
these functions is provided. Is this understanding correct?

-Wayne Pollock

James Kanze

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
Dietmar Kuehl wrote:

> So basically your complaint about C++ comes down to the missing
> "comparable" class? In some sense, I agree that the standard C++
> library is definitely not as complete as it could be. In another sense
> I think that the standard C++ library is actually quite big.

Well, if you wanted to go this route, you'd need to add quite a few
base classes: EqualityComparable (for == and !=), InequalityComparable
(derives from EqualityComparable, but adds <, <=, > and >=), Hashable
(derives from EqualityComparable, but adds hash), and so on. It could
very easily get out of hand.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
Dietmar Kuehl wrote:

> James Kanze (James...@dresdner-bank.de) wrote:
>: Dietmar Kuehl wrote:

>: > It is actually
>: > equally important, that the arguments are not required to be derived
>: > from some specific class/interface/however you call it, like eg. the
>: > "COMPARABLE": They are only required to conform to a certain abstract
>: > concept, for 'max()' is the concept of being "less than comparable".

>: Why is it important not to give this concept a name, and require that
>: the class state explicitly that it implements it.

> There would be no problem if the concepts were merely tags which are
> added to the class' list of bases (except for built-in types; it
> might be possible to add this to the core language that eg. 'int'
> seems to derive from 'copy_constructible', 'copy_assignable',
> etc.). Going beyond just tags, eg. adding any declaration to the
> base class which is more than just some form of tag becomes to
> restricted: There is no point in restricting the interface in any
> way. Since we where stuck with the "comparable" property, here is an
> example for this: Assume that we want to make sure that classes
> implementing "comparable" are indeed implementing a comparison
> operator. Using C++ syntax this would look like this:

> struct comparable {
> bool operator< (comparable const&) const = 0;
> };

> This has two problems:

> - The obvious one is that the overriding function gets a
> "comparable" as second argument which dwarfs static type checking:
> It would be possible to pass any comparable object not just one of
> the same type. I'm not sure whether the given Eiffel construct
> prevents this but in C++ additional syntax would be required to
> guarantee at compile time that only identical types can be
> compared.

Yes. You probably need something like covariant arguments, as well as
co-variant return types.

> - The not so obvious problem is the return type: Why I'm I forced to
> return a Boolean? For my high precision numbers I have a tri state
> return:

> enum compare_result {
> failed = 0, // not true
> epsilon_difference, // true but a rather close call (*)
> significant_difference, // true witout any doubt
> huge_difference // several orders of magnitude different
> };
> // (*) difference in the order of computing precision only

> For some applications it might be acceptable to consider this to
> be just two states (and for those the use of 'max()' should
> definitely be an option) but for others it might be crucial to
> deal eg. with the minor difference (eg. by doing another compare
> the other way around to determine whether the numbers might
> actually be considered to be identical).

Here, IMHO, you've violated the contract. A boolean is a boolean, and
nothing else is a boolean. (Note that this is different from the case
with the arguments, where the actual argument is a comparable, just
not any comparable.) If you need this, then it should be implemented
in another function, perhaps meeting an additional interface:
ApproxamatelyComparable, or some such.

At any rate, as I indicated in another posting, the real problem is
where do you stop?

> That is, defining anything about the interface can swiftly become a
> limiting factor. ... and something as simple as a "comparable" only
> shows the tip of the problem! You definitely don't want to create
> artificial constraints.

On the other hand, you'd like to be able to enforce real constraints.
With templates, this isn't too much of a problem. If the real
constraints are violated, you will normally get an error message at
compile time. The message may not be too understandable, but at
least, you will know that something is wrong before the code gets out
the door. With inheritance, it is more difficult. If Comparable is
only a tagging interface, what happens if I derive from it, but forget
to implement the necessary function?

I've not really made up my mind as to what the best solution in an
ideal language would be. In C++, I don't generally make use of such
interfaces, except where I actually need the common base class,
because it is not the accepted idiom, and any possible gains would be
small enough to not outweigh the disadvantages of not using the
accepted idiom.

>: Remember the problems we have (and to a degree still have) because
>: auto_ptr is not copy assignable, although it implements the operator=
>: for other purposes. If we had some sort of abstract base class that
>: specified "copy assignable", the problem would never have arisen;
>: auto_ptr wouldn't have derived from this class.

>: > In C++ the following works:

>: > struct Dog {
>: > void barf() const { std::cout << "barf!\n"; }
>: > bool operator< (Dog const&) const { return true; }
>: > };

>: > max(Dog(), Dog()).barf();

>: > For generic programming it is crucial that this works, in particular
>: > it is necessary that the return type of 'max()' is of type 'Dog'.

>: No. Generic programming works with dynamic type checking as well.

> It does not. Here is an example (to really run it, some more syntax is
> necessary):

> int numbers() { static int rc = 0; return rc++; }

> std::vector<int> array(10);
> std::generate_n(array.begin(), 10, numbers);

> std::sort(std::find(array.begin(), array.end(), 3),
> std::find(array.begin(), array.end(), 7),
> std::greater<int>());

> This works in C++ but does not work if dynimic typing is used, at least
> not with the same guarantees:

Who said anything about the same guarantees? If you have dynamic
typing, the compile time guarantees of static typing aren't there.
Generic programming or not.

I was, in fact, thinking of languages like Common Lisp or Smalltalk
when I made the statement. From what little I know of the languages,
both have stricter type systems than C++.

> In C++ we get a compile time error, if we
> replace "vector" by "list". With dynamic typing we get a run time error
> assuming the C++ constraints for "sort()" do not change (ie. the

> arguments have to be random access iterators). However, it is more than


> just the compile time error: Although not necessarily in this example
> but with others we also get an appropriate algorithm selected for the

> arguments - at compile time.

What if the language doesn't have a "compile time." (I think that
this is sort of true for both Lisp and Smalltalk.)

For large scale projects, I definitly prefer static type checking.
And static checking of anything else that can be checked as well. And
I don't find it a disadvantage to have to state explicitly which
interfaces my class implements, rather than having it implicitly
implement all interfaces, and then getting a run-time error "not
supported" when I call a function it doesn't actually implement. But
this is orthogonal to generic programming.

> You can program generically using dynamic polymorphism but not in
> the in the sense of Generic Programming. You can try to mimick the
> behavior necessary for Generic Programming using dynamic
> polymorphism but it does not really work that well. And in any case,
> you loose one of the important properties of generic programming:
> The code is no longer as fast as code written specifically for a
> particular type.

Again, I think you are putting too much into the term generic
programming. It's about like some of the early OO enthusiasts
claiming that dynamic typing was necessary, because the language which
first popularized OO (Smalltalk) had dynamic typing.

And I don't think performance has anything to do with programming
style, as such. Generic programming may or may not be as fast as what
you can write otherwise. The speed penalty, at least in C++, is
general minimal, but not necessarily. And of course, in a language
with dynamic typing, there is no speed *penalty* for generic
programming either, since you also have to evaluate the types
dynamically in non-generic idioms.

Finally, of course, most dynamic languages have compilers which do
some sort of type induction, precisely to eliminate the cost in the
frequent cases. Practically speaking, they need it so that simple
things like integer arithmetic don't have unacceptable overhead (since
their integers are generic objects, remember). But of course, all
generic programs profit from it.

[...]
>: It's a lot more dangerous, but it works.

>: > If it
>: > does work for Eiffel, Eiffel might be usable to do generic
>: > programming. However, this is not the only restriction. It is also
>: > necessary that certain operation return types depending on the exact
>: > type of the function arguments, not just some type know to a base
>: > class.

>: But it isn't necessary for this dependance to be evaluated at compile
>: time.

> It is. See above.

>: > For example, the function accessing an element identified by an
>: > iterator (in C++ 'operator*()') has to return a type depending on the
>: > underlying sequence: If the iterator is eg. moving over a sequence
of
>: > 'int's, the result of accessing an element has to be a 'int', too.

>: No. It has to return a type that can be used as an int. Generic
>: programming works in Smalltalk, for example.

> No, it does not, at least not in the sense the term "Generic
> Programming" is used in Matt Austern's book or by Alexander
> Stepanov. You can program generically in Smalltalk but this does not
> mean that you can do Generic Programming with Smalltalk. The compile
> time part of the algorithms is indeed crucial for the successful
> application of this paradigm.

It's critical to ensure the acceptance of the paradigm in certain
circles. I don't think that it has anything to do with whether
something is or is not a certain program style. (To look at it from
the opposite side: the fact that C++ is faster than Smalltalk doesn't
mean that you cannot do OO programming in C++.)

Walter E Brown

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
"Louis A. Russ" wrote:
<snip>
> ... what if you have:
> Inches yardInInches(36.);
> Yards oneYard(1.);
> Length &l1 = yardsInInches;
> Length &l2 = oneYard;
> Length &l3 = max(l1,l2);
>
> Maybe that last line should be:
> Length &l3 = std::max<Length>(l1,l2);
<snip>

By international agreement (which has the force of treaty), a "quantity"
is
a measurable attribute. "Length" is a commonly-used basic quantity
defined
by this agreement, and is certainly suitable for implementation and use
as a
data type as shown in the above code fragment.

However, "Inches" and "Yards" are not quantities in the same sense,
since
neither is a measureable attribute; instead, these are names for the
_results_ of particular measurements of Length.

The same international agreement referred to above (_Systeme
international
d'unites_) defines "units" for use in measuring quantities. (Units
must,
of course, be compatible with the quantities they are used to measure;
it
would be silly to measure Length in Quarts, for example.) I would
therefore
argue that any unit is merely an instance of the corresponding
quantity: an
"inch" is a particular measurement of Length, as is a "yard."

Using these insights, we now have:
#include <algorithm>
Length len1 = 36 * inch;
Length len2 = 1 * yard;
Length len3 = std::max(len1,len2);
which is, of course, perfectly reasonable. In addition, one would like
such constructs as:
std::cout << ((len1 == len2) ? "yes" : "no");
Volume box = len1 * len2 * len3;
which are equally reasonable and should work as you'd expect.

FWIW, my package (named SIunits) handles all of this, and more. I
expect
version 5 to be finished in late December/early January and will seek
permission to make it publicly available, probably via the Boost
organization.
Questions and constructive comments related to SIunits are always
welcome.

- WEB

Artur Biesiadowski

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
James Kanze wrote:

>
> What more is there to say. One of the recognized weaknesses of C++ is
> that the separation isn't great enough -- the class definition must
> also contain information that doesn't concern the class users, like
> the declaration of private data members. Over time, we've developed
> idioms to work around these limitations, like the compilation firewall
> idiom. Given that this partial lack of separation is recognized as a
> weakness in C++, I find it surprising that Java would intentionally
> make the situation much worse.

Java answer to it is javadoc. In c++ you have header and main file. In
theory header should specify only public interface and main file
implementation details. Unfortunately it is need to put some internals
inside header (like private fields).

Java source files is comparable to main file. You put all details there
- only class implementor should ever look there. If you are a class
user, you look at automatically generated javadoc. In worst case it
contains just list of all protected and public fields and methods (if
class designer has not provided any additional comments), in best case
every one is fully commented with examples etc.

Of course in case of lack of javadoc comments and general poor design
you might be forced too look up implementation details to understand how
to use class - but this is a case even with best language if contract is
not specified good enough.

Artur

James Kanze

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
Greg wrote:

> > Good programming languages let you use code such as
> > myAsciiString < myEbcdicString
> > which would evaluate to true or false.

> Well, we're talking C++ and Eiffel, and I've never seen this in


> either. If you know of a C++ package that does this, show me.

The strings don't, but the iostream's for wide characters do. Each
stream has a locale which defines (amongst other things) how to
convert between the external (byte oriented) representation and the
internal wide character representation.

(The problem, as presented, is wrong. You never want to handle
different code sets within the program. You convert at the IO level.
If support for several code sets is necessary, the internal code set
must cover them all. Typically this means wide characters and
Unicode.)

> I'm especially interested in how it handles characters that are in
> one standard and not in the other.

Convert both to Unicode, and compare that.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
Walter E Brown wrote:

> Using these insights, we now have:
> #include <algorithm>
> Length len1 = 36 * inch;
> Length len2 = 1 * yard;
> Length len3 = std::max(len1,len2);
> which is, of course, perfectly reasonable. In addition, one would like
> such constructs as:
> std::cout << ((len1 == len2) ? "yes" : "no");
> Volume box = len1 * len2 * len3;
> which are equally reasonable and should work as you'd expect.

That's neet. Now we throw in a few manipulators:

std::cout << si::inches << len3 << " inches\n" ;
std::cout << si::meters << len3 << " meters\n" ;
// ...

> FWIW, my package (named SIunits) handles all of this, and more. I
> expect version 5 to be finished in late December/early January and
> will seek permission to make it publicly available, probably via the
> Boost organization.

Does it implement the manipulators? If not, do have have plans for it
to do so. If you're not familiar with the techniques (using xalloc to
manage space for the state), drop me a line, and I'll see if I can
help. (I don't have the time to actually do any development, but I
can answer questions, and have some example code which could set you
on the right track.)

One other interesting idea: my manipulators generally reset the stream
to the initial format state at the end of the full expression. It's
something worth thinking about, although since it is definitly
non-standard behavior, it may be more confusing than anything else for
a general package.

James Kanze

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
Artur Biesiadowski wrote:

> James Kanze wrote:

> > What more is there to say. One of the recognized weaknesses of
> > C++ is that the separation isn't great enough -- the class
> > definition must also contain information that doesn't concern the
> > class users, like the declaration of private data members. Over
> > time, we've developed idioms to work around these limitations,
> > like the compilation firewall idiom. Given that this partial lack
> > of separation is recognized as a weakness in C++, I find it
> > surprising that Java would intentionally make the situation much
> > worse.

> Java answer to it is javadoc.

How does javadoc allow different people to be responsible for the
class definition and the implementation?

Dietmar Kuehl

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
Hi,
bran...@cix.compulink.co.uk wrote:
: ku...@ramsen.informatik.uni-konstanz.de (Dietmar Kuehl) wrote (abridged):
: > However, it is more than just the compile time error: Although

: > not necessarily in this example but with others we also get
: > an appropriate algorithm selected for the arguments

: This is more to do with multiple dispatch than static typing. Admittedly


: Smalltalk is a single-dispatch language, so we should really be looking
: at Dylan, Cecil, CLOS for this kind of thing.

Where do you put the "sort function" (or however a corresponding
construct would be called in the respective language) in Dylan, Cecil,
CLOS? Is it dependent on a collection type or is it a freestanding,
global function applying to all containers allowing random access? How
do these languages cope with varying return types to accomodate
different types in a collection? (my guess, is the system does not
really care about the type at all or uses a common base class like
Smalltalk's 'Object' - however, Smalltalk does not care about return
types at all) What is the performance of the generic sort function in
Dylan, Cecil, CLOS compared to 'std::sort()' and compared to sort
functions implemented specifically for certain data types? Is the
generic sort function as easy to use as one being specific for a
certain data type?

These are questions for which I think I can guess the answers. If my
guesses are right, these languages are not interesting for generic
programming at all. If I'm guessing wrong for at least most questions
it might be worth looking into these languages for generic programming
purposes

: As you say, it is not necessary in this example. Nor in many others. The


: single-dispatch subset of Generic Programming, supported by Smalltalk, is
: quite useful.

For certain kinds of applications. For generic programming it is rather
useless: The most complex algorithms found in the STL are actually what
I would consider to be trivial algorithms! Real complex algorithms have
much more requirements and are much more complex. ... and in most cases
single dispatch is simply insufficient. My guess is, that dynamic is
too expensive in most of these cases too, but I haven't measured it
(the guess is derived from the performance figures of allowing or
disallowing certain functions to be inlined - just the function call,
no dynamic dispatch coming into play - in areas where flexibility is
required).

: > - at compile time.

: Yes, the algorithm selection is logically done at runtime in a dynamic
: multimethod language.

: Later you talk about speed differences. As far as I can tell you are
: talking about the costs due to using a poorer algorithm, due to not being
: able to use special algorithms in special cases. This kind of speed cost
: can be avoided in Dylan etc because it does let us do the
: specialisations. In fact, because it is dynamic, we can use the faster
: algorithm in situations where C++ would not.

There are several things which impact performance. My premier concern
is in fact that the "right" algorithm is used because this can give
rather dramatical difference in the asymptotical behavior which often
reflect directly the size of problems which can be hoped to be tackled.
For example, if the program can use an algorithm for a planar graph
rather than one for a general graph, the better algorithm is often
linear or maybe quadratic while the general algorithm is typically some
higher order polynom (if it is polynomial at all).

However, this is only part of the story: The best generic algorithm
should be as fast as the best algorithm specially crafted for a certain
data structure. This is a pretty strong property which is not [yet?]
always achieved (but the areas I have looked into, using C++ typically
comes pretty close). From forcing function calls rather than allowing
certain function to be inlined (in areas of variability, ie. something
where a dynamic approach would be required to have dynamic dispatch
for achieving the same flexibility) it becomes visible that inlining
is actually crucial to achieve the required runtime behavior.

: There may also be a speed cost due to runtime overheads in the dispatch


: mechanism. However, now we're comparing implementations not languages.
: Good optimisers should produce good code even for dynamic languages,
: especially if they happen to have available the kind of static info which
: C++ demands.

Well, what might be available is the [possibly distant] future does
not help me with current projects. Although there is no inherit reason
why certain languages really have to be slower than others (if languages
show different run-time time for the same input to the same algorithm,
all you need is a better optimizer and maybe better written code, after
all), this does not help me...

: >: No. It has to return a type that can be used as an int. Generic


: >: programming works in Smalltalk, for example.
: >
: > No, it does not, at least not in the sense the term "Generic
: > Programming" is used in Matt Austern's book or by Alexander
: > Stepanov. You can program generically in Smalltalk but this does
: > not mean that you can do Generic Programming with Smalltalk.
: > The compile time part of the algorithms is indeed crucial for
: > the successful application of this paradigm.

: I agree that Smalltalk isn't quite up to it, but the problem is not


: dynamic versus static typing but single versus multiple dispatch.

This is part of the problem. I claim that the dynamic dispatch is too
expensive in the first place. To determine this, you can stay within
one language, BTW: Implement the same algorithm in a generic fashion
and in a fashion tailored to a specific data structure. Then measure
the performance of both implementations for the same input on the data
structure you have the tailored implementation for. Normally, it is
useful to use a second data structure to verify that the generic
algorithm is indeed generic (ie. develop and test the generic algorithm
with a different data structure than this one you are measuring the
performance with).

: This is important in the context of Eiffel because Eiffel *is* statically


: type checked. It even has some support for genericity. However, it lacks
: multiple dispatch. Multiple dispatch is an advantage C++ has over Eiffel.

However, compared to some other languages, C++ lacks support for
dynamic multiple dispatch: C++ "only" has static multiple dispatch.


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dietmar Kuehl

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
Hi,

James Kanze (James...@dresdner-bank.de) wrote:
: Dietmar Kuehl wrote:
: > - The obvious one is that the overriding function gets a

: > "comparable" as second argument which dwarfs static type checking:
: > It would be possible to pass any comparable object not just one of
: > the same type. I'm not sure whether the given Eiffel construct
: > prevents this but in C++ additional syntax would be required to
: > guarantee at compile time that only identical types can be
: > compared.

: Yes. You probably need something like covariant arguments, as well as
: co-variant return types.

The only problem is that covariant arguments would violate the LSP: If
you tighten the rules for the arguments, you cannot pass the general
arguments accepted by the function in the base class. You can change
the argument type, however: You can make it more general. I have seen
this to be called "contravariance" but I'm not into theory that much to
determine whether this is the correct term.

: > - The not so obvious problem is the return type: Why I'm I forced to


: > return a Boolean? For my high precision numbers I have a tri state
: > return:

: > enum compare_result {
: > failed = 0, // not true
: > epsilon_difference, // true but a rather close call (*)
: > significant_difference, // true witout any doubt
: > huge_difference // several orders of magnitude different
: > };
: > // (*) difference in the order of computing precision only

: > For some applications it might be acceptable to consider this to
: > be just two states (and for those the use of 'max()' should
: > definitely be an option) but for others it might be crucial to
: > deal eg. with the minor difference (eg. by doing another compare
: > the other way around to determine whether the numbers might
: > actually be considered to be identical).

: Here, IMHO, you've violated the contract. A boolean is a boolean, and
: nothing else is a boolean.

Except, however, that a typical requirement says something like "<"
forms a "strict weak ordering". This is true for the results given
above! There is no requirement that "a < b" yields a Boolean! In C++,
the result of "a < b" has to be "convertible to bool" (see 20.1.2 for a
reference) to qualify for a "LessThanComparable".

: At any rate, as I indicated in another posting, the real problem is
: where do you stop?

I'm not arguing for introducing all those tiny definitions! I'm quite
comfortable what we have in C++ now.

: And I don't think performance has anything to do with programming


: style, as such. Generic programming may or may not be as fast as what
: you can write otherwise.

At least Alexander Stepanov considers the performance to be an
important traits of the generic algorithms: "Generic programming is a
programming method that is based in finding the most abstract
representations of efficient algorithms. That is, you start with an
algorithm and find the most general set of requirements that allows it
to perform and to perform efficiently." (taken from
<http://www.stlport.org/resources/StepanovUSA.html>). ... and from my
experience with graph algorithms which are typically built upon other
graph algorithms (at the bottom there are normally some variations of
graph search algorithms) I agree with him that performance is a very
important factor: There is no benefit in a generic implementation if it
runs several orders of magnitude slower! Many real world problems
already take days or even weeks to be solved - with the fastest
available implementation. For these problems being solved generically
but much slower is simple inacceptable (well, it is, of course, not
that black and white: it may be feasable to implement something using
some generic implementations but it may not be feasable to implement
the same thing from scratch at all - however, I think you get the
drift).

: The speed penalty, at least in C++, is


: general minimal, but not necessarily. And of course, in a language
: with dynamic typing, there is no speed *penalty* for generic
: programming either, since you also have to evaluate the types
: dynamically in non-generic idioms.

Maybe the dynamic language is not an appropriate language for generic
programming in the first place...? Actually, I remember reading on of
Stepenov's paper where he states the goal for a generic implementation
to be as fast as the fastest possible implementation of a specific
algorithm - including handcrafted special purpose assembler! There is a
focus on performance, actually a pretty strong one. It is fairly easy
to come up with working abstractions for typical problem domains but it
is much harder to come up with abstractions which do not impose
performance penalties. Generic programming is not only about
flexibility - it is about flexibility at not runtime overhead.

: It's critical to ensure the acceptance of the paradigm in certain


: circles. I don't think that it has anything to do with whether
: something is or is not a certain program style. (To look at it from
: the opposite side: the fact that C++ is faster than Smalltalk doesn't
: mean that you cannot do OO programming in C++.)

... and does C++ really stay faster than Smalltalk if you are using
algorithms based on object oriented approaches? Note, that I'm not
disputing the use of object orientation (here I'm refering basically to
dynamic dispatch when saying "object orientation" - encapsulation,
[static] polymorphis, etc. still have their place)! It is great for
certain things. But as far as I can tell, it is useless for the
implementation of many algorithms, although the algorithms my operate
on data structures using object oriented approaches. Basically, it
comes down to the same old theme: A hammer is not the solution to all
problems - a fact hard to accept for those educated only in the use of
a hammer. Currently, hammers are still fashionable although there are
people outside which clearly show that a screwdriver also has its
place. Not to force nails into walls but this is not the claim
anyway...


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Eirik Mangseth

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
This is where most Eiffel environments excels as they define
various views on a class:

Short: only exported features (current class)
Flat: Every feature in current class including ancestor(s)
Short/Flat: only exported features (current class + ancestor(s))

All the above includes any pre- and postcondtions as well
as class invariants. You can view a class within the IDE or
you can export it (or its cluster or the whole system) using a rich
number of formats. Another interesting view that we hopefully
will see implemented in the IDE is a Class-relationship view.
It would be even more interesting to be able to edit in the
Class-relationship view. Done proplerly, the need for expensive
(and often useless :) case-tools would be obviated as everything
would be at your fingertips.

Eirik Mangseth
Stockpoint Nordic AS

"If I can't Eiffel in heaven, I won't go"

"Artur Biesiadowski" <ab...@pg.gda.pl> wrote in message
news:3A0B2183...@pg.gda.pl...


> James Kanze wrote:
>
> >
> > What more is there to say. One of the recognized weaknesses of C++ is
> > that the separation isn't great enough -- the class definition must
> > also contain information that doesn't concern the class users, like
> > the declaration of private data members. Over time, we've developed
> > idioms to work around these limitations, like the compilation firewall

> > idiom. Given that this partial lack of separation is recognized as a


> > weakness in C++, I find it surprising that Java would intentionally
> > make the situation much worse.
>

> Java answer to it is javadoc. In c++ you have header and main file. In
> theory header should specify only public interface and main file
> implementation details. Unfortunately it is need to put some internals
> inside header (like private fields).
>
> Java source files is comparable to main file. You put all details there
> - only class implementor should ever look there. If you are a class
> user, you look at automatically generated javadoc. In worst case it
> contains just list of all protected and public fields and methods (if
> class designer has not provided any additional comments), in best case
> every one is fully commented with examples etc.
>
> Of course in case of lack of javadoc comments and general poor design
> you might be forced too look up implementation details to understand how
> to use class - but this is a case even with best language if contract is
> not specified good enough.
>
> Artur
>

Matthew Austern

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
"Eirik Mangseth" <no_spa...@stockpoint.no> writes:

> This is where most Eiffel environments excels as they define
> various views on a class:
>
> Short: only exported features (current class)
> Flat: Every feature in current class including ancestor(s)
> Short/Flat: only exported features (current class + ancestor(s))

But this has exactly the same features (both good and bad) that James
Kanze identified with Java: a class's interface and implementation are
physically intermixed in the same file. They can't be separately
managed by source control systems; they can't be the responsibility of
two separate groups. For some designs, and some organizational
structures, this isn't a problem. For others it is.

External tools, like javadoc and short, don't affect this issue.

Gabriel Dos Reis

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
James Kanze <James...@dresdner-bank.de> writes:

[...]

| >: But in practice, you only want the const version, I would think.
|
| > No, you want both (this point was missed by others in this thread,
| > too): If you want to change the result of the 'max()' function, you
| > want to get a non-const reference.
|
| But I don't want to change the result of the max() function. That is
| exactly my point. The only use for changing the result of the max()
| function I can think of is code obfuscation.

Agreed. But isn't C++ syntax "code obfuscation "in the first place? ;-)

More seriously, max() should be cousidered a function (not just in the
C++ sense but in the mathematical sense) : it takes values and returns
a value. Period. For optimization purpose, it is understandable to
make it take references instead of values but that should not be an
excuse to mutate its semantics.

--
Gabriel Dos Reis, dos...@cmla.ens-cachan.fr

bran...@cix.compulink.co.uk

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
ku...@ramsen.informatik.uni-konstanz.de (Dietmar Kuehl) wrote (abridged):
> Where do you put the "sort function" (or however a corresponding
> construct would be called in the respective language) in Dylan, Cecil,
> CLOS? Is it dependent on a collection type or is it a freestanding,
> global function applying to all containers allowing random access?

I'm going to stick with Dylan, because it is more mature and commercial
than Cecil and I don't know CLOS as well.

In Dylan, conformance is explicit and there is a universal base class
<object>. The standard sort is defined on <sequence>, which declares
conformance to basic iteration protocols. The core language would allow
it to be more general, but the library designers preferred tighter error
checking.

This is different to C++, but arguably better. C++ could not work this
way because of backwards compatibility issues with C, and with old
linkers. For example, C-arrays couldn't be fitted into a class system
retrospectively.

C++'s std::sort does impose a specific protocol on what is sorted, but
the protocol is not very explicit. Having to declare it in Dylan does not
so much create a new dependency as document an existing one.


> How do these languages cope with varying return types to accomodate
> different types in a collection? (my guess, is the system does not
> really care about the type at all or uses a common base class like
> Smalltalk's 'Object' - however, Smalltalk does not care about return
> types at all)

Static type-checking is optional in Dylan. Since I'm arguing that
genericity is possible in a dynamic system, we'll say "does not care" for
now.


> What is the performance of the generic sort function in Dylan,
> Cecil, CLOS compared to 'std::sort()' and compared to sort
> functions implemented specifically for certain data types?

I don't know specifically for sort. I do know the same generic mechanisms
are used for Dylan's equivalent of for-loops etc, and when these generic
functions are applied to specific containers like <simple-vector>, and
enough type info is available at compile time, the machine code generated
by current compilers is equal to that of C++. Eg dynamic dispatches will
be resolved at compile-time, functions inlined etc.

Dylan was designed by smart people to be fast. In that respect, the
culture is different to that of Smalltalk. The attitude is more like that
of Stepanov, that we shouldn't have to pay runtime costs for good
abstraction. Dylan has features, such as optional type annotations,
"sealing", and compile-time hygienic macros, which help programmers help
the optimiser. (It also avoids some of C++'s aliasing problems... C++ is
not the last word in speed by any means.)


> Is the generic sort function as easy to use as one being specific for
> a certain data type?

Yes.


> These are questions for which I think I can guess the answers. If my
> guesses are right, these languages are not interesting for generic
> programming at all. If I'm guessing wrong for at least most questions
> it might be worth looking into these languages for generic programming
> purposes

I think it would especially be worth looking at Dylan.

I know Stepanov tried to write the STL in a variety of languages, and was
not satisfied. However, that was a while ago. I don't know if Dylan was
available at all back then, and if it was, whether he tried it. I'd love
to hear his opinion on it today.


> The best generic algorithm should be as fast as the best
> algorithm specially crafted for a certain data structure. This is

> a pretty strong property [...]

The core idea of Generic Programming is the specialisation of algorithms.
There is a "generic style" in the same way that there is an "object
oriented style", and neither is defined in terms of speed.

Efficiency is good, but at this level it is largely quality of
implementation stuff.

Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Greg

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
In article <3A0BBA6F...@dresdner-bank.de>,
James Kanze <James...@dresdner-bank.de> wrote:
[...]

>
> (The problem, as presented, is wrong. You never want to handle
> different code sets within the program. You convert at the IO level.
> If support for several code sets is necessary, the internal code set
> must cover them all. Typically this means wide characters and
> Unicode.)

What about an MVS application running against a commercial database
stored in ASCII (because the database also targets NT and Unix
platforms), and the application is restricted to compilers that don't
support wide characters or Unicode in any way shape or form?

>
> > I'm especially interested in how it handles characters that are in
> > one standard and not in the other.
>
> Convert both to Unicode, and compare that.

This is nice if you can get it. But you can't always get what you want.
Then there's still issues about collating rules and comparison rules.
Should EBCIDIC collating sequences prevail (that's what the MVS
programmer would like to see) or ASCII (that's what the database will
expect) or if you have it, Unicode (which may differ in details from
the other two, especially for characters that don't appear in both
standards.)

I've also been thinking about the issue of LSP with respect to this
thread. People have been attacking the artificial Animal heirarchy as a
violation of LSP. This is true "in the small." After all, I've kept the
case simple and restricted it to three classes.

Eiffel and C++ take two different views about object heirarchies.
Eiffel, like SmallTalk and Java, has a universal base class (ANY) that
all new classes automatically inherit from. By way of this, every class
gets access to many common facilities, such as type identification,
basic type comparison, deep and shallow copy functions, equality
checks, access to standard file I/O, etc, etc. This is the "one big
tree of objects" approach.

On the other hand, C++ treats each new class as an island. The compiler
will build in some basic facilities (such as a default copy
constructor), but beyond that, if a programmer wants access to a common
set of facilities, they have to be provided manually. This is the "lots
of shrubs" of objects approach.

I really don't want to get into a debate on the pros and cons of the
two approaches. I'm just talking now about their affect on how one
thinks about the design of a program.

I believe the effect of this is that in C++, LSP is an overriding
concern when designing class inheritance. The inheritance heirarchy
should be deliberately constructed to satisfy LSP, and only tightly
coupled classes shold reside in that heirarchy. The heirarchy will be
short, narrow and tightly controlled.

Certainly an Eiffel programmer will also look for such heirarchies and
strive to achieve LSP for a collection of tightly coupled classes. But
the inheritance heirarchy may be much deeper, and MI may be used far
more often in an Eiffel program than in a corresponding C++ program.

So in an Eiffel program, having checks that violate LSP "in the small"
may make sense "in the large." As an extreme case, any program that
guaranteed LSP at the level of class ANY would either be a) trivial, b)
useless or c) a mutant non-OO program in an Eiffel dress.

--
http://homestead.deja.com/user.gmc333/index.html


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Greg

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
In article <8ucako$dnt$4...@news.BelWue.DE>,
dietma...@yahoo.com wrote:
[...]

>
> The reason to return a non-const reference is to allow modifications
to
> the result of 'max()', not allowing 'max()' to change the arguments.
> Although the standard C++ library only provides the const version, I
> think it should provide both versions.

You're right that both versions are needed. With just the const
version, a simple expression like
int a = 5, b = 6;
int c = max(a,b);
fails, since the C is not const and the assignment can't be legally
made without resorting to a cast.

Matt Kennel

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
-0500, Gabriel Dos Reis <dos...@cmla.ens-cachan.fr> wrote:

:James Kanze <James...@dresdner-bank.de> writes:
:
:[...]
:
:| >: But in practice, you only want the const version, I would think.

:|
:| > No, you want both (this point was missed by others in this thread,
:| > too): If you want to change the result of the 'max()' function, you
:| > want to get a non-const reference.
:|
:| But I don't want to change the result of the max() function. That is
:| exactly my point. The only use for changing the result of the max()
:| function I can think of is code obfuscation.
:
:Agreed. But isn't C++ syntax "code obfuscation "in the first place? ;-)

:
:More seriously, max() should be cousidered a function (not just in the
:C++ sense but in the mathematical sense) : it takes values and returns
:a value. Period. For optimization purpose, it is understandable to
:make it take references instead of values but that should not be an
:excuse to mutate its semantics.

That makes sense if the arguments are members of value classes themselves.

In other cases, the desired semantics is 'which of these two pointers
to objects points to the object with the maximum value?'

Either of those seems like a reasonably interpretation.

C++ 'references' make my head hurt though.

--
* Matthew B. Kennel/Institute for Nonlinear Science, UCSD
*
* "To chill, or to pop a cap in my dome, whoomp! there it is."
* Hamlet, Fresh Prince of Denmark.

Dietmar Kuehl

unread,
Nov 11, 2000, 3:00:00 AM11/11/00
to
Hi,
Greg (gmc...@my-deja.com) wrote:
: You're right that both versions are needed. With just the const

: version, a simple expression like
: int a = 5, b = 6;
: int c = max(a,b);
: fails, since the C is not const and the assignment can't be legally
: made without resorting to a cast.

This does not require the non-const version: Copying a const object to
a non-const object typically works. There are only few exceptions to
this rule (eg. 'std::auto_ptr' is one). The only reason to have the
non-const version is to allow changing of the result. Although some
people call it code obfuscation, I think it is the easiest approach to
change the maximum value...


--
<mailto:dietma...@yahoo.de>
<http://www.fmi.uni-konstanz.de/~kuehl/>
I am a realistic optimist - that's why I appear to be slightly pessimistic

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Artur Biesiadowski

unread,
Nov 11, 2000, 3:00:00 AM11/11/00
to
James Kanze wrote:

> How does javadoc allow different people to be responsible for the
> class definition and the implementation?

It doesn't - you are right. I haven't catched the main point of question
- I was focused on "the class definition must
also contain information that doesn't concern the class users", not on
the thing you written in first paragraph.

In some cases you can use interface. All public methods goes there,
together with javadoc comments. It is created by designer. Actual
implementation implements them plus add some internal details as needed
by coder. But I agree it does not scale to all problems - designers also
need to specify some other things that just public methods. But in most
cases I think it should be ok.

BTW, do any of mainstream languages solve this in reasonable manner ? I
vaguely remember Modula-something having something like this (but it is
hardly mainstream language :)

Artur

bran...@cix.compulink.co.uk

unread,
Nov 11, 2000, 3:00:00 AM11/11/00
to
aus...@research.att.com (Matthew Austern) wrote (abridged):

> But this has exactly the same features (both good and bad) that James
> Kanze identified with Java: a class's interface and implementation are
> physically intermixed in the same file. They can't be separately
> managed by source control systems; they can't be the responsibility of
> two separate groups. For some designs, and some organizational
> structures, this isn't a problem. For others it is.
>
> External tools, like javadoc and short, don't affect this issue.

A traditional OO answer is to use inheritance. That is, the first group
produces an abstract base class which specifies the interface and the
second group a concrete subclass which implements it. (Meyer calls it
"Reification inheritance".) The Short/Flat tool might make this approach
easier to work with, because it makes inheritance easier to work with.

In Eiffel the interaction between inheritance and Design by Contract will
help too. Even without these benefits the same approach can be used in
Java or C++. I've never understood why James Kanze rejects it. To me it
seems a natural form of information hiding. I don't see why a language
needs extra features to do the same thing.

Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Greg

unread,
Nov 12, 2000, 1:08:50 AM11/12/00
to
In article <8uif18$jev$3...@news.BelWue.DE>,

dietma...@yahoo.com wrote:
> Hi,
> Greg (gmc...@my-deja.com) wrote:
>: You're right that both versions are needed. With just the const
>: version, a simple expression like
>: int a = 5, b = 6;
>: int c = max(a,b);
>: fails, since the C is not const and the assignment can't be legally
>: made without resorting to a cast.
>
> This does not require the non-const version: Copying a const object to
> a non-const object typically works. There are only few exceptions to
> this rule (eg. 'std::auto_ptr' is one). The only reason to have the
> non-const version is to allow changing of the result. Although some
> people call it code obfuscation, I think it is the easiest approach to
> change the maximum value...

Typo: meant
int a= 5, b = 6;
int& c = max(a,b);

Just the general notion that the result of max can't be assigned to a
non-const reference.


> --
> <mailto:dietma...@yahoo.de>
> <http://www.fmi.uni-konstanz.de/~kuehl/>
> I am a realistic optimist - that's why I appear to be slightly pessimistic
>

> [ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> [ about comp.lang.c++.moderated. First time posters: do this! ]
>
>

--
http://homestead.deja.com/user.gmc333/index.html


Sent via Deja.com http://www.deja.com/
Before you buy.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Greg

unread,
Nov 12, 2000, 3:00:00 AM11/12/00
to
In article <dily9yr...@isolde.research.att.com>,
Matthew Austern <aus...@research.att.com> wrote:

> "Eirik Mangseth" <no_spa...@stockpoint.no> writes:
>
> >
> > Short: only exported features (current class)
> > Flat: Every feature in current class including ancestor(s)
> > Short/Flat: only exported features (current class + ancestor(s))
>
> But this has exactly the same features (both good and bad) that James
> Kanze identified with Java: a class's interface and implementation are
> physically intermixed in the same file. They can't be separately
> managed by source control systems; they can't be the responsibility of
> two separate groups. For some designs, and some organizational
> structures, this isn't a problem. For others it is.
>

Couldn't you use a Java interface class to solve this problem? Then
certainly one party can define the interface and another can develop the
implementation. The two are then entirely separate entities.

The reason C++ has include files is for backward compatibility with C, and
the reason C has include files is because way back when they developed the
compiler, it was easier for the implementors to place the burden on the
programmers instead of the compiler. In return compilation ran faster and
took up much less memory.

Are there any other languages designed in the last 15 years that have
insisted on the include file dichotomy?

Greg

James Rogers

unread,
Nov 12, 2000, 3:00:00 AM11/12/00
to
Greg wrote:
>
> Couldn't you use a Java interface class to solve this problem? Then
> certainly one party can define the interface and another can develop the
> implementation. The two are then entirely separate entities.
>
> The reason C++ has include files is for backward compatibility with C, and
> the reason C has include files is because way back when they developed the
> compiler, it was easier for the implementors to place the burden on the
> programmers instead of the compiler. In return compilation ran faster and
> took up much less memory.
>
> Are there any other languages designed in the last 15 years that have
> insisted on the include file dichotomy?
>
> Greg

I infer from this statement that you think the C include file technology
is
the only technology used to separate interface from implementation.

You suggested using the Java interface approach. Unfortunately, that is
not
quite what is needed. Interfaces have a lot of other implications, and
cannot be easily used for separating the interface and implementation of
EVERY class you define.

One of the goals of OO design is information hiding. To that end OO
languages
have access modifiers such as "private", "protected" and "public".
Another part of information hiding is the separation of interface and
implementation. Java classes have no general syntax for the separation
of
implementation and interface. You must explicitly define an interface,
which can be implemented by any class, completely independent of any
inheritance
hierarchy. Used this way, the interface does not specify the entire
interface
to a class, but only part of the interface to a class.

C++ does use the old C include technology. On the other hand, that
technology is fully capable of defining the full interface for each
class,
not just a specialized subset of the interface as can be done for Java.

Other technologies of interface definition exist and are quite
successful.
Ada requires the separation of interface and implementation by declaring
two
different compilation units. Every stand alone Ada subprogram, package,
task, or protected object must be described in a specification section
and then
implemented in a "body" section.

Example:

(A Specification section for Ada:)

task Print_Handler is
entry Write (Message : in String);
entry Stop;
end Print_Handler;

(An implementation of the Print_Handler task:)

task body Print_Handler is
Text : String(1..1024);
Size : Natural;
begin
loop
select
accept Write (Message : in String) do
Size := Message'Length;
Text(1..Size) := Message;
end Write;
Ada.Text_IO.Put_Line(Text(1..Size));
or
accept Stop;
exit;
end select;
end loop;
end Print_Handler;

---------

Note that the above example completely separates the implementation and
the
interface. The Print_Handler task may be interfaced with exactly two
entries.
One prints a message, the other stops the Print_Handler task. The
implementation
defines how this is accomplished. By convention the implementation and
the
specification are put in separate files. You can compile a program
consisting
entirely of implementation files. No executable will be made, but you
can
ensure that the implementations are all syntactically correct. You can
then
implement bodies in separate programming teams, with all teams knowing
how to
deal with all the entities being made by other teams.

The separation of specification and interface is intended to produce
just this
sort of result. Such capabilities are critical for large scale
programming
efforts. Languages without clear separation of interface and
specification
must invent other tools to try to compensate. Java invented the javadoc
tool
to try to document a class interface separate from the implementation of
the
class. Unfortunately, the quality of the javadoc interface description
depends
upon the quality of javadoc comments placedd in the ".java" source file.
With strong programmer discipline a javadoc documentation file can
approach the
quality of information contained in the actual code in a C++ .h file or
an
Ada specification. Unfortunately, javadoc files usually fall far short
of this
goal.

Jim Rogers
Colorado Springs, Colorado

Thomas Beale

unread,
Nov 12, 2000, 3:00:00 AM11/12/00
to


Matthew Austern wrote:

"Eirik Mangseth" <no_spa...@stockpoint.no> writes:

> This is where most Eiffel environments excels as they define
> various views on a class:
>

> Short: only exported features (current class)
> Flat: Every feature in current class including ancestor(s)
> Short/Flat: only exported features (current class + ancestor(s))

But this has exactly the same features (both good and bad) that James
Kanze identified with Java: a class's interface and implementation are
physically intermixed in the same file. They can't be separately
managed by source control systems; they can't be the responsibility of
two separate groups. For some designs, and some organizational
structures, this isn't a problem. For others it is.

This is an interesting point. Years ago I was part of a team that worked
with K&R C for years,
and eventually, through our study of how to use it better (by writing
ADTs, adding assertions for
example), it dawned on us one day that one of the giant problems was the
separation of the
header and implementation files.

This separation appeared to be a management nightmare in ANSI C & C++,
where the headers
carried a full interface description, since it breaks one of the basic
laws of information
maintenance: never duplicate. This separation clearly leads to maintenance
errors, and also
forces the use of a unix-style make system which figures out all the
intricate dependencies.

In the years I have used Eiffel, the ability to extract interface views of
various kinds from single
class files, and the freedom from make files has been wonderful.

However, I realised some years ago that something was missing: that there
was no way to
separately control (i.e. change-manage) the interfaces and implementations
of classes. Or, more
precisely, there seemed no easy way to separate the decisions made at the
analysis and
architectural design level, from those made by implementors; i.e.:

- know which things were decided by analysis/design level, and preserve
their semantics
- know which things were not mandated, and which can be freely changed by
implementors.

This doesn't work on a strictly per-class basis, if one looks at the
problem. The real need is for
anaylsis level work to be able to produce a model, and to then proceed
with implementation of
that model, with the knowledge that the semantics in the analysis model
are complied to.

As an example: the anaylsis team might decide that in a particular model,
the following is
needed:

class ORGANISATION
-- ANALYSIS level class
feature -- Access

subordinates: LIST [ORANISATION]

end

The question is here what are the rules for implementation of this class:
what can be changed?
One approach seems obvious enough: inherit from the analysis class, and
allow the rules of type
conformance through inheritance to guide what implementors can do. In the
above example, this
is quite nice in Eiffel; it is easy to see that implementors still have
the freedom to choose a
particular kind of LIST[], while inheritance will prevent them
substituting a SET or some other
semantically different thing. Other class features can be added, as one
would expect. Eiffel in
fact would allow renaming of the original feature, but one might
reasonably ban this if
developers were uncomfortable with it.

The above might be achieved by calling the analysis class A_ORGANISATION,
and the
implementation class ORGANISATION.

But there is a problem: there may well be inheritance in the "A_" model;
will this conflict with
inheritance in the implementation model?

In Eiffel, it turns out that the answer is "no". The trick is to define
every feature in the analysis
classes as deferred (virtual for the C++ people), even those which
analysis seems to think can
be known as a simple data member.

If this simple rule is followed, it is possible for an entire
implementation model (set of classes)
to inherit from an entire analysis model (another set of classes). Since
they are separate sets of
classes, they can be separately controlled under a configuration
management system. When an
implementor thinks s/he has realised that subordinates:LIST[ORGANISATION]
should really
have been subordinates:SET[ORGANISATION] or even
subordinates:SET[AARDVARK],
they will find that their system won't compile; then that to make it
compile, they would have to
check out and and change an "A_" class. The CM system or change management
process will
prevent this, and will force a higher level change control meeting to
decide whether the change
is justified or not.

This seems to be exactly what is needed. Can it work?

On the Good Electronic Health Record (GEHR; http://www.gehr.org) we have
done exactly
this. If you look at
http://www.gehr.org/technical/source/Documentation/gehr_root/index.html,
you can follow the "g_gom" link for the analysis classes, and the
"k_kernel" link for
implementation classes. The kernel implementation inherits completely from
the gom analysis
model; all the classes are controlled in separate groups in our
configuration management
system.

I have not done any theoretical work on this approach, nor have I
determined whether it could
work in other languages, but at least in a "class = 1 file language" such
as Eiffel, it works
without any problems.

Even nicer: we can write tools to read the "G_" classes to create XML
specifications, CORBA
interface definitions and so on, knowing we are capturing only the desired
analysis
specification.

I would be interested to know about others' experiences with an approach
like this; also what
the theoretical rules for it would be. For example, it is not actually
strictly necessary to make
100% of analysis class features abstract, but I am not sure of what the
rules are for choosing
which are and which are not. Also, the applicability to languages with
different inheritance
rules needs to be investigated. Alternative approaches to this problem are
certainly possible,
for example a source coding system in which each file is no longer a file,
but managed in a
database, with a tagging system saying what can and cannot be changed.

In summary, let me restate the problem here as I see it, in a way that
should make it clear for
future work:
* to enable separate development and change management of groups of
classes (models)
corresponding to different abstraction levels of software development
(typically
analysis/design and implementation), while retaining the formal technical
relationship between
the models (e.g. as defined by inheritance or some other set of rules).

Note: I must acknowledge two things in taking this approach: when we were
trying it for the
first time, it was Dr Sam Heard, the clinical lead on the GEHR project who
said to me "can't
you just make everything deferred in the G_ classes?". As a software
engineer it hadn't felt
right, but I did it, and it worked.

Secondly, the approach I have mentioned above is really what you find in
the Eiffel Base
libraries, designed in large part by Bertrand Meyer. If you look
carefully, you will see that even
the success of my example above is because abstractions like LIST, SET and
implementation
versions of same, are designed in the library along similar principles.
Unfortunately, I didn't
understand until we had done it ourselves that we had not actually
invented something new....

- thomas beale

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Artur Biesiadowski wrote:

> James Kanze wrote:

> > How does javadoc allow different people to be responsible for the
> > class definition and the implementation?

> It doesn't - you are right. I haven't catched the main point of question
> - I was focused on "the class definition must
> also contain information that doesn't concern the class users", not on
> the thing you written in first paragraph.

> In some cases you can use interface. All public methods goes there,
> together with javadoc comments. It is created by designer. Actual
> implementation implements them plus add some internal details as
> needed by coder. But I agree it does not scale to all problems -
> designers also need to specify some other things that just public
> methods. But in most cases I think it should be ok.

For once a reasonable response to my comments:-). (In
comp.lang.java.programmer, similar comments just draw flames.) I feel
fairly sure that in time, Java programmers will come up with a Java
idiom to solve the problem, just as C++ programmers came up with the
compilation firewall idiom to solve the problem in C++. It's just
that, given the Java compilation model, one wonders what sort of
perverse reasoning led to the problem in the first place. (The
"perverse reasoning" for C++'s weaknesses in this regard is C
compatibility.)

> BTW, do any of mainstream languages solve this in reasonable manner
> ? I vaguely remember Modula-something having something like this
> (but it is hardly mainstream language :)

I'm not sure how many languages actually say anything about how the
source is split up into different files. Ada supports a separate
public and private definition of packages. Modula-3 does the same
with interfaces (public) and modules (private); I think that this is
unchanged from Modula-2. I would expect any implementation of either
language to support putting the different parts in different files.

It's probably also worth pointing out that some languages take a
completely different approach. They count entirely on
comments/documentation for the interface specification, and do all
actual verification at run-time. Intuitively, the idea seems wrong to
me, but languages like Smalltalk and Lisp have been used successfully
on some very large projects.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Dietmar Kuehl wrote:

> James Kanze (James...@dresdner-bank.de) wrote:
>: Dietmar Kuehl wrote:
>: > - The obvious one is that the overriding function gets a
>: > "comparable" as second argument which dwarfs static type checking:
>: > It would be possible to pass any comparable object not just one of
>: > the same type. I'm not sure whether the given Eiffel construct
>: > prevents this but in C++ additional syntax would be required to
>: > guarantee at compile time that only identical types can be
>: > compared.

>: Yes. You probably need something like covariant arguments, as well as
>: co-variant return types.

> The only problem is that covariant arguments would violate the LSP:
> If you tighten the rules for the arguments, you cannot pass the
> general arguments accepted by the function in the base class. You
> can change the argument type, however: You can make it more
> general. I have seen this to be called "contravariance" but I'm not
> into theory that much to determine whether this is the correct term.

Yes. That's what I meant. I had vague memories that it had a
different name, but I couldn't remember what.

>: > - The not so obvious problem is the return type: Why I'm I forced to
>: > return a Boolean? For my high precision numbers I have a tri state
>: > return:

>: > enum compare_result {
>: > failed = 0, // not true
>: > epsilon_difference, // true but a rather close call (*)
>: > significant_difference, // true witout any doubt
>: > huge_difference // several orders of magnitude different
>: > };
>: > // (*) difference in the order of computing precision only

>: > For some applications it might be acceptable to consider this to
>: > be just two states (and for those the use of 'max()' should
>: > definitely be an option) but for others it might be crucial to
>: > deal eg. with the minor difference (eg. by doing another compare
>: > the other way around to determine whether the numbers might
>: > actually be considered to be identical).

>: Here, IMHO, you've violated the contract. A boolean is a boolean, and
>: nothing else is a boolean.

> Except, however, that a typical requirement says something like "<"
> forms a "strict weak ordering". This is true for the results given
> above! There is no requirement that "a < b" yields a Boolean! In
> C++, the result of "a < b" has to be "convertible to bool" (see
> 20.1.2 for a reference) to qualify for a "LessThanComparable".

In order to support "strict weak ordering", an object is either less
than another, or it isn't. Sounds like a boolean to me.

Alexander Stepanov is only one author, and he has a very particular
viewpoint. Performance is important for certain algorithms, and less
for others.

There are also two aspects with regards to performance. If it is only
the constant factor that changes, machines in a few years will wipe
out the difference. When Stepanov speaks of "efficient algorithms",
however, he is not speaking of the constant factor, but the big-O.
Big-O performance is almost always important, as soon as the data set
becomes large. But dynamic dispatch has no effect on the big-O; it
only changes the constant factor. With an efficient compiler, the
change of the constant factor may even be minimal.

>: The speed penalty, at least in C++, is
>: general minimal, but not necessarily. And of course, in a language
>: with dynamic typing, there is no speed *penalty* for generic
>: programming either, since you also have to evaluate the types
>: dynamically in non-generic idioms.

> Maybe the dynamic language is not an appropriate language for generic
> programming in the first place...?

There are many applications where I don't consider dynamic languages
appropriate. But not for performance reasons.

> Actually, I remember reading on of
> Stepenov's paper where he states the goal for a generic implementation
> to be as fast as the fastest possible implementation of a specific
> algorithm - including handcrafted special purpose assembler!

This isn't a question of programming style, but of the efficiency of
the optimizer.

> There is a
> focus on performance, actually a pretty strong one. It is fairly easy
> to come up with working abstractions for typical problem domains but it
> is much harder to come up with abstractions which do not impose
> performance penalties. Generic programming is not only about
> flexibility - it is about flexibility at not runtime overhead.

Then it probably isn't of any real interest to most programmers. I
think it is interesting, regardless of the performance aspect.

>: It's critical to ensure the acceptance of the paradigm in certain
>: circles. I don't think that it has anything to do with whether
>: something is or is not a certain program style. (To look at it from
>: the opposite side: the fact that C++ is faster than Smalltalk doesn't
>: mean that you cannot do OO programming in C++.)

> ... and does C++ really stay faster than Smalltalk if you are using
> algorithms based on object oriented approaches?

For most implementations, yes. Of course, this probably has more to
do with the fact that a lot more money and effort are expended on C++
compilers than on Smalltalk compilers, rather than any intrinsic
difference in the language.

> Note, that I'm not
> disputing the use of object orientation (here I'm refering basically to
> dynamic dispatch when saying "object orientation" - encapsulation,
> [static] polymorphis, etc. still have their place)! It is great for
> certain things. But as far as I can tell, it is useless for the
> implementation of many algorithms, although the algorithms my operate
> on data structures using object oriented approaches.

IMHO, by definition, an algorithm is procedural. First do A, then B,
then C. And as far as I know, at some level, something procedural
will happen. In many cases, I imagine that the procedural goings on
will also be of interest to the programmer, and he will want to
influence them. (This is true even in Prolog, for example. While the
language doesn't directly offer any traditional procedural operations,
the procedural effects of different logical operations are well
documented, and used by the programmer to eliminate dead branches
quickly.)

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Dietmar Kuehl wrote:

> This is part of the problem. I claim that the dynamic dispatch is
> too expensive in the first place. To determine this, you can stay
> within one language, BTW: Implement the same algorithm in a generic
> fashion and in a fashion tailored to a specific data structure. Then
> measure the performance of both implementations for the same input
> on the data structure you have the tailored implementation
> for. Normally, it is useful to use a second data structure to verify
> that the generic algorithm is indeed generic (ie. develop and test
> the generic algorithm with a different data structure than this one
> you are measuring the performance with).

An interesting annecdote (but only an annecdote): a few years back I
implemented a simplified version of returning nodes in a parse tree
for things like operator+ over a Matrix. I did so once using
templates, and once using inheritance, and benchmarked them. Using
Sun CC 4.2, the version using inheritance was measurably faster.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Greg wrote:

> In article <3A0BBA6F...@dresdner-bank.de>,
> James Kanze <James...@dresdner-bank.de> wrote:
> [...]

> > (The problem, as presented, is wrong. You never want to handle
> > different code sets within the program. You convert at the IO
> > level. If support for several code sets is necessary, the
> > internal code set must cover them all. Typically this means wide
> > characters and Unicode.)

> What about an MVS application running against a commercial database
> stored in ASCII (because the database also targets NT and Unix
> platforms), and the application is restricted to compilers that
> don't support wide characters or Unicode in any way shape or form?

I'm not sure I understand the question. A C/C++ implementation is
required to support wchar_t in order to be conform. And while the
standard really doesn't make any requirements with regards to wchar_t,
I've never heard of an implementation where it wasn't Unicode.

> > > I'm especially interested in how it handles characters that are
> > > in one standard and not in the other.

> > Convert both to Unicode, and compare that.

> This is nice if you can get it. But you can't always get what you
> want. Then there's still issues about collating rules and
> comparison rules. Should EBCIDIC collating sequences prevail
> (that's what the MVS programmer would like to see) or ASCII (that's
> what the database will expect) or if you have it, Unicode (which may
> differ in details from the other two, especially for characters that

> don't appear in both standards.)

You have two choices: you can use the native collating sequence, in
which case, comparing an ASCII string and an EBCDIC string has no
meaning. Or you can use the localized collating sequences, and they
can be just about anything the programmer wants.

[...]


> So in an Eiffel program, having checks that violate LSP "in the
> small" may make sense "in the large." As an extreme case, any
> program that guaranteed LSP at the level of class ANY would either
> be a) trivial, b) useless or c) a mutant non-OO program in an Eiffel
> dress.

I'm not sure you have understood LSP. In Java, it is considered very
bad practice to write a class which doesn't respect LSP with regards
to Object, and I'm sure that this is true in Eiffel as well. Any
class which implements Object *should* be usable everywhere the
interface specifies Object -- in Java, for example, this means that if
I redefine equals, I also redefine hashCode with a compatible
definition. Otherwise, I have violated the LSP, since Object can be
used in a Hashtable, but my derived class cannot.

Of course, I've already posted that without more context, I don't
think that one can say whether your example violates LSP. One could
argue that it violates good naming conventions: operator< on Animal?
But I think you made it clear that it was an artificial example, and
such artificial examples are often used to clarify points.

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Gabriel Dos Reis wrote:

> James Kanze <James...@dresdner-bank.de> writes:

> [...]

> | >: But in practice, you only want the const version, I would think.

> | > No, you want both (this point was missed by others in this
> | > thread, too): If you want to change the result of the 'max()'
> | > function, you want to get a non-const reference.

> | But I don't want to change the result of the max() function. That
> | is exactly my point. The only use for changing the result of the
> | max() function I can think of is code obfuscation.

> Agreed. But isn't C++ syntax "code obfuscation "in the first place?
> ;-)

As I said in another posting, C++'s strength is its ability to meet
many styles. I guess obfuscation is also one of the styles it
supports.

FWIW: obfuscation is one style that every language seems to support,
more or less well (and generally more, even if not always as well as
C++). I like to think that my C++ is readable, however. And while
not reknown for its readability, the cleanest, most readable program I
ever saw was written in Fortran IV, with only six character variable
names, no block if's, no while loops, etc. Where there's a will
there's a way.

> More seriously, max() should be cousidered a function (not just in
> the C++ sense but in the mathematical sense) : it takes values and
> returns a value. Period. For optimization purpose, it is
> understandable to make it take references instead of values but that
> should not be an excuse to mutate its semantics.

This is my feeling, too.

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
James Rogers wrote:

> Greg wrote:

> > Couldn't you use a Java interface class to solve this problem?
> > Then certainly one party can define the interface and another can
> > develop the implementation. The two are then entirely separate
> > entities.

> > The reason C++ has include files is for backward compatibility

> > with C, and the reason C has include files is because way back


> > when they developed the compiler, it was easier for the
> > implementors to place the burden on the programmers instead of the
> > compiler. In return compilation ran faster and took up much less
> > memory.

> > Are there any other languages designed in the last 15 years that
> > have insisted on the include file dichotomy?

> I infer from this statement that you think the C include file


> technology is the only technology used to separate interface from
> implementation.

> You suggested using the Java interface approach. Unfortunately, that
> is not quite what is needed. Interfaces have a lot of other
> implications, and cannot be easily used for separating the interface
> and implementation of EVERY class you define.

The essential problems are that you cannot instantiate an interface,
and that you cannot specify pre- and post-conditions and class
invariants in an interface, other than as simple comments. With
regards to the first, when I raised the issue once in
comp.lang.java.programmer, I did receive some concrete suggestions (as
opposed to a majority of flames for daring to suggest that Java wasn't
perfect). Unlike the situation in C++ (where the compilation firewall
is widely known), there doesn't seem, at present, to be a more or less
standard idiom to work around the problem.

With regards to the second aspect, programming by contract: it is
significant that javadoc has no tags for pre- and post-conditions, nor
for class invariants.

[...]


> C++ does use the old C include technology. On the other hand, that
> technology is fully capable of defining the full interface for each
> class, not just a specialized subset of the interface as can be done
> for Java.

Fully capable, yes. On the condition that the programmers exercise
the necessary discipline. And it is far too easy and frequent to find
more than just the interface contract in the header file; use of the
compilation firewall idiom isn't universal (nor should it probably
be; a Complex which requires a dynamic allocation in the constructor
is probably not a winner for time critical numeric applications).

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Greg wrote:

> In article <dily9yr...@isolde.research.att.com>,


> Matthew Austern <aus...@research.att.com> wrote:
> > "Eirik Mangseth" <no_spa...@stockpoint.no> writes:

> > > Short: only exported features (current class)
> > > Flat: Every feature in current class including ancestor(s)
> > > Short/Flat: only exported features (current class + ancestor(s))

> > But this has exactly the same features (both good and bad) that James
> > Kanze identified with Java: a class's interface and implementation are
> > physically intermixed in the same file. They can't be separately
> > managed by source control systems; they can't be the responsibility of
> > two separate groups. For some designs, and some organizational
> > structures, this isn't a problem. For others it is.

> Couldn't you use a Java interface class to solve this problem? Then


> certainly one party can define the interface and another can develop
> the implementation. The two are then entirely separate entities.

Perhaps. In certain cases, certainly. I'm looking into it. But the
idiom hasn't yet jelled, as has the compilation firewall idiom. Only
time will tell.

> The reason C++ has include files is for backward compatibility with
> C, and the reason C has include files is because way back when they
> developed the compiler, it was easier for the implementors to place
> the burden on the programmers instead of the compiler. In return
> compilation ran faster and took up much less memory.

> Are there any other languages designed in the last 15 years that
> have insisted on the include file dichotomy?

Are there any other languages designed in the last 15 years that

insist on everything being file based? Modula-3 and Ada both have
very separate interface sections, that can easily be implemented as
separate files in a file based compilation system.

Nobody, I think, is claiming that C++ has the perfect system. It is,
in the end, no different than the COBOL copy verb. What some of us
don't understand is why a modern language without the constraint of
backwards compatibility would intentionally choose to do worse.

Matthew Austern

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Greg <gmc...@my-deja.com> writes:

> Couldn't you use a Java interface class to solve this problem? Then
> certainly one party can define the interface and another can develop the
> implementation. The two are then entirely separate entities.

Again, that doesn't quite solve the same problem. The original
question is: split a class's interface and implementation into two
different files that can be managed separately. You have now answered
a different question: derive one class for another, and put the base
and derived class in two different files.

This isn't quite the same thing, either in Eiffel, Java, or C++. One
thing all three languages have in common is that if you want to create
an object of a derived class, whatever creates the object has to know
about the derived class. (You don't have virtual constructors in any
of those languages.) So if you've decided to separate a class's
implementation and interface by splitting off a base class, you have
to figure out some way to work around the virtual constructor
issue---probably using factories or exemplars or something like that.
This can quickly get complicated.

Daniel T.

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
In article <3A0E613E...@deepthought.com.au>, Thomas Beale
<tho...@deepthought.com.au> wrote:


> I would be interested to know about others' experiences with an approach
> like this; also what the theoretical rules for it would be.

You might want to check out the papers at www.objectmentor.com as well as
Rober C. Martins book, _Designing Object Oriented C++ Applications Using
the Booch Method_

Greg & Ying Compestine

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to

James Rogers wrote:

> Greg wrote:
> >
> > Couldn't you use a Java interface class to solve this problem? Then
> > certainly one party can define the interface and another can develop
the
> > implementation. The two are then entirely separate entities.
> >

> > The reason C++ has include files is for backward compatibility with C,
and
> > the reason C has include files is because way back when they developed
the
> > compiler, it was easier for the implementors to place the burden on the
> > programmers instead of the compiler. In return compilation ran faster
and
> > took up much less memory.
> >
> > Are there any other languages designed in the last 15 years that have
> > insisted on the include file dichotomy?
> >

> > Greg


>
> I infer from this statement that you think the C include file technology
> is
> the only technology used to separate interface from implementation.
>

James, your inference is *very* wrong. What I'm trying to say is that the
C/C++
approach of declaring interface in an include file has a historical basis
in the
limits of technology and that it is probably the _worst_ way to achieve
this. It
is an incredible source of error. It is a maintenance nightmare. It allows
grotesque intermingling of implementation with the interface. In short, it
sucks.

Ada is only a slight improvement. At least you can't place implementation
into
the specification.

Both languages require duplication of information, which is Not Good. I'm
saying
that languages that don't do this are doing something better. If Java does
a
poor job of automating this separation, don't blame the principle, blame
the
language.

Greg

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
Eirik Mangseth wrote:

> This is where most Eiffel environments excels as they define various
> views on a class:

> Short: only exported features (current class) Flat: Every feature in


> current class including ancestor(s) Short/Flat: only exported
> features (current class + ancestor(s))

Is this a feature of the language, or of the implementation? Sniff+
allows this in C++.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

James Kanze

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
bran...@cix.compulink.co.uk wrote:

> aus...@research.att.com (Matthew Austern) wrote (abridged):

> > But this has exactly the same features (both good and bad) that James
> > Kanze identified with Java: a class's interface and implementation are
> > physically intermixed in the same file. They can't be separately
> > managed by source control systems; they can't be the responsibility of
> > two separate groups. For some designs, and some organizational
> > structures, this isn't a problem. For others it is.

> > External tools, like javadoc and short, don't affect this issue.

> A traditional OO answer is to use inheritance.

A run-time solution to a process problem?

> That is, the first group produces an abstract base class which
> specifies the interface and the second group a concrete subclass
> which implements it. (Meyer calls it "Reification inheritance".) The
> Short/Flat tool might make this approach easier to work with,
> because it makes inheritance easier to work with.

> In Eiffel the interaction between inheritance and Design by Contract
> will help too. Even without these benefits the same approach can be
> used in Java or C++. I've never understood why James Kanze rejects
> it. To me it seems a natural form of information hiding. I don't see
> why a language needs extra features to do the same thing.

I don't reject it. If the problem exists, you find the best
work-around. In C++, the problem exists, moderately, and the
work-around is well known, having been documented by Lakos, Sutter and
no doubt others. In Java (and Eiffel?) the problem doesn't even seem
to have been acknowledged; I'm sure that once acknowledged, workable
solutions will be found in Java (and Eiffel?).

I also think that Matt's closing words are the real key: "For some


designs, and some organizational structures, this isn't a problem.

For others it is." Requirements differ, and C++'s real strength isn't
so much how well it supports one set of requirements, but how easily
it can be adopted to a wide variety of requirements. Thus, I'm fully
convinced, even without knowing the language, that Eiffel has better
support for programming by contract than does C++. It was designed
for that. But how well does it score on other important issues. Java
scores higher than C++ on portability, but renders programming by
contract impossible, because the designers didn't think it important.

For almost any given requirement, I suspect that there is a better
language than C++. So the question is: what did the designers of C++
think important. And I think that the answer is flexibility. The one
thing I've never heard from Stroustrup and company is "this is the
only correct way to program."

It is loading more messages.
0 new messages