January 22, 2002
One of the things I really like about the genericity/template system in Eiffel is so-called "Constrained Genericity".  For the full description, you can probably find information at www.eiffel.com or in "Object Oriented Software Construction, 2nd Edition" (otherwise known as OOSC2), but an extremely short example, converted to C++'ish syntax would be:

template <typename T:SORTABLE> T max(T a, T b)
{
	return a<b ? b : a;
}

The only non-C++ code I meant to be present in the preceding code is the use of "typename T:SORTABLE" instead of just "typename T".  (If I got anything else wrong, it is just a typo, and not significant)  This mechanism can also be applied to template classes (which is the primary use of it in Eiffel), but the max() example is shorter and simpler.

What this means is that this template can only be instantiated with types that are SORTABLE or are derived from SORTABLE.  Since max() uses operator< on type T, we need to know that type T supports operator<.  C++ does this at the point of call, which is OK as far as it goes, but the above syntax has a few advantages:

1. What a type IS is more than just the collection of available operators.  In the example, SORTABLE specifically means that operator< provides a total ordering (I can't remember strong/weak definitions, so I leave them out of this).  Other types may have operator< that doesn't provide a total ordering, and would not derive from SORTABLE, and would not be appropriate for use here, even though C++ would compile it.

2. Because the constraints appear in the declaration of the template, users of max() know that they can only use it on types that are SORTABLE.  Self-documenting code is good...

3. Because the relevant interface is in the declaration, it *should* be easier for compilers to provide more direct and meaningful error messages.  I prefer "Error : templatized type T in max() must be of type SORTABLE." over "Error : operator< is not defined for type FLEEM".  This may just be a personal style kind of thing.

I seem to remember that you can use the "like" keyword to introduce covariance constraints, as well.  For example, the previous template could also be written as:

template <typename T1:SORTABLE, T2:like T1> T1 max(T1 a, T2 b)
{
	return a<b ? b : a;
}

The primary addition here is the second parameterized/templatized type, which is also constrained, but is constrained to be "like" the first type (T1).  This means that T2 can be the same as T1, or it can be any type derived from T1 (I think -- sorry, my Eiffel is a little rusty).  This is not especially meaningful for max(), but it can be very useful for properly expressing constraints on generic classes that need to manipulate different types, but need those types to have specific relationships to each other.

I gave the above template form because Eiffel uses the "like" keyword, but it looks unnecessary (and is, in that context).  After all, the declaration could just be:

template <typename T1:SORTABLE, T2:T1> T1 max(T1 a, T2 b)

and it would mean exactly the same thing (T2 is of type T1 or is derived from T1).  The "like" keyword is generally used inside templatized classes to add members that are constrained to be "like" one of the templatized types.

I have wandered, and I need to go read OOSC2 again, but the central point (for those of you with the patience to put up with me this far) is that Constrained Genericity and Type Covariance are useful constructs that do not exist in C++ (without some painful twisting around) and that I would like to see added to D.

Thank you for your time and consideration,
Mac Reiter
Software Engineer
January 22, 2002
"Mac Reiter" <reiter@nomadics.com> wrote in message news:3c4d7a82.405408206@news.digitalmars.com...
> One of the things I really like about the genericity/template system in Eiffel is so-called "Constrained Genericity".  For the full description, you can probably find information at www.eiffel.com or in "Object Oriented Software Construction, 2nd Edition" (otherwise known as OOSC2), but an extremely short example, converted to C++'ish syntax would be:
>
> template <typename T:SORTABLE> T max(T a, T b)
> {
> return a<b ? b : a;
> }
>
> The only non-C++ code I meant to be present in the preceding code is the use of "typename T:SORTABLE" instead of just "typename T".  (If I got anything else wrong, it is just a typo, and not significant)  This mechanism can also be applied to template classes (which is the primary use of it in Eiffel), but the max() example is shorter and simpler.
>
> What this means is that this template can only be instantiated with types that are SORTABLE or are derived from SORTABLE.  Since max() uses operator< on type T, we need to know that type T supports operator<.  C++ does this at the point of call, which is OK as far as it goes, but the above syntax has a few advantages:
>
> 1. What a type IS is more than just the collection of available operators.  In the example, SORTABLE specifically means that operator< provides a total ordering (I can't remember strong/weak definitions, so I leave them out of this).  Other types may have operator< that doesn't provide a total ordering, and would not derive from SORTABLE, and would not be appropriate for use here, even though C++ would compile it.
>
> 2. Because the constraints appear in the declaration of the template, users of max() know that they can only use it on types that are SORTABLE.  Self-documenting code is good...
>
> 3. Because the relevant interface is in the declaration, it *should* be easier for compilers to provide more direct and meaningful error messages.  I prefer "Error : templatized type T in max() must be of type SORTABLE." over "Error : operator< is not defined for type FLEEM".  This may just be a personal style kind of thing.
>
> I seem to remember that you can use the "like" keyword to introduce covariance constraints, as well.  For example, the previous template could also be written as:
>
> template <typename T1:SORTABLE, T2:like T1> T1 max(T1 a, T2 b)
> {
> return a<b ? b : a;
> }
>
> The primary addition here is the second parameterized/templatized type, which is also constrained, but is constrained to be "like" the first type (T1).  This means that T2 can be the same as T1, or it can be any type derived from T1 (I think -- sorry, my Eiffel is a little rusty).  This is not especially meaningful for max(), but it can be very useful for properly expressing constraints on generic classes that need to manipulate different types, but need those types to have specific relationships to each other.
>
> I gave the above template form because Eiffel uses the "like" keyword, but it looks unnecessary (and is, in that context).  After all, the declaration could just be:
>
> template <typename T1:SORTABLE, T2:T1> T1 max(T1 a, T2 b)
>
> and it would mean exactly the same thing (T2 is of type T1 or is derived from T1).  The "like" keyword is generally used inside templatized classes to add members that are constrained to be "like" one of the templatized types.
>
> I have wandered, and I need to go read OOSC2 again, but the central point (for those of you with the patience to put up with me this far) is that Constrained Genericity and Type Covariance are useful constructs that do not exist in C++ (without some painful twisting around) and that I would like to see added to D.
>
> Thank you for your time and consideration,
> Mac Reiter
> Software Engineer


I was thinking of something else that looks a lot like what you are
saying, but concerns operator oveloading. I'm gonna start a new
thread about it as to not go too much off topic on yours, but what
you are saying sounds very good to me. Read the new thread:
Operator overloading: A way to make everybody happy?
I'm going to point people from there to here, because I think these
two subjects are related.


--
Stijn
OddesE_XYZ@hotmail.com
http://OddesE.cjb.net
__________________________________________
Remove _XYZ from my address when replying by mail