challenge: implement the max function
Kevin Bealer
kevinbealer at gmail.com
Wed Jan 24 22:32:08 PST 2007
Andrei Alexandrescu (See Website For Email) wrote:
> Frits van Bommel wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> Here's a simple challenge: implement the max function. Requirements:
>>>
>>> a) generic
>>> b) efficient
>>> c) preserve lvalueness when possible such that one can write e.g.
>>>
>>> max(arr[0], arr[1]) *= 0.9;
>>>
>>> d) should accept two or more arguments
>>> e) should return the "smartest" type, e.g. max of an unsigned int and
>>> unsigned long should return unsigned long
>>> f) short and easy to understand
>>>
>>> I don't think it's possible to implement the function to the spec in
>>> current D, so designs are allowed to invent new features, as long as
>>> they define them thoroughly.
...
> Heck, I did it in C++. It takes some hundred plus lines of code though
> :o). The obvious purpose of my challenge is to find what it takes to
> make it a trivial few-liner in D.
>
>
> Andrei
Here's my late entry:
alias max_type(T, U) {
static if (T.max > U.max) {
alias T max_type;
} else {
alias U max_type;
}
}
max_type(T, U) ALIAS max(T, U)(inout T x, inout U y)
{
alias max_type(T,U) MT;
MT x1 = x, y1 = y;
if (x1 >= y1) {
static if (is (MT == T)) {
return alias x;
} else {
return x1;
}
} else {
static if (is (MT == U)) {
return alias y;
} else {
return y1;
}
}
}
The new feature is "return alias x", which is just syntax for saying
that you are returning a reference to the input parameter.
Note that "ALIAS" should be replaced by some keyword -- or maybe remvoed
entirely, since it can be deduced easily as described below. It should
be safe to remove it entirely since we can deduce it from the I was
going to use "alias" but it looks too much like If D ever gets a keyword
to indicate the opposite of C++'s "strict" keyword, I would think it
could be used here.
A. Generic
As long as "U + T" is legal, I think this could work. A better choice
of the return value could be made if you expect "char[]" to be used, but
otherwise, I think it's a pretty generic solution. If constants are
passed into the function, the "static if" needs to fail somehow.
B. Efficient
Consider this simple saltshaker...
One way this could work from a stack packing point of view, is to
imagine that the code actually returns this object:
struct real_return_t {
max_type(T,U) * _proxy;
max_type(T,U) _data;
}
Then, plain old "return XYZ" does this:
real_return_t rv;
rv._data = XYZ;
rv._proxy = & rv._data;
But the "return alias XYZ" does this:
real_return_t rv;
rv._proxy = & x; // input arg
In the calling code, the return value of max() is the dereference of
rv._proxy, so that max(a, b) becomes *(max(a, b).proxy), thus
allowing the assignment.
Optimization:
1. If T == U, then both returns end up being "return alias", which means
that the caller never uses the 'rv._data' member; it can be eliminated
and the function becomes T * max(...), with automagic dereferencing of
the return value.
2. If typeof(T + U) is neither T nor U (think byte + uint -> int), then
the rv.data field is always used, which means the return value is always
equivalent to "*& rv._data". This means that the member rv._proxy can
be optimized away. Thus, it could becomes normal int max(...).
3. If T == MT but T != U, or vice versa, then the real_return_t object
cannot be removed, but we essentially have a variant return type, and a
hand-written implementation would normally use the same technique to
return either the input primitive or another one.
So, in cases 1-3, I think it is as efficient as we could expect from a
hand-written job.
C. Lvalueness
I think so. This preserves it when possible, gets rid of it when not,
and does so in a way that is highly readable. NOTE that this solution
preserves lvalue-ness above-and-beyond because for (int x, long y), it
preserves it for y even though it can't preserve it for x.
D. Two or More arguments
I didn't code up this part but my gut reaction solution is to use a
static foreach (do those exist yet?) in the max() function and some kind
of recursive template dance in the max_type template.
E. I think using "+" is enough for this if classes are not involved. If
they are then I don't think deducing the max of two classes is possible
without cooperation from them. However, one could try to assign in both
directions and see which assignment "hurts less" with something like SFINAE.
F. As long as you don't count this post... but the code looks tidy.
------
Solution Two
NOTE: This is not a C++ "&", see below.
template max(T, U) {
static if (T == U) {
T & max(T & x, U & y)
{
if (*x >= *y) {
return x;
} else {
return y;
}
}
} else {
max_type(T, U) max(T, U)(T x, U y)
{
alias max_type(T,U) MT;
if (x >= y) {
return x;
} else {
return y;
}
}
}
}
The "&" here is kind of like C++'s "&" type, but you can't use it except
in function interfaces. In the context of a function argument, it means
that the caller of the function is actually passing "& x" when they say
"x", and in the case of a return value, the caller thinks they get
something like "T x", but it they actually get a "T * x", and when they
use it, the "x" turns into "*x".
Inside the function body, no magic translations are done, the writer of
the function use actual pointers to do the work to make it harder to
accidentally return addresses of local variables.
I think this satisfies A B C E F, though not as heroicly with C as the
Solution One (which may be a good thing.)
Kevin
More information about the Digitalmars-d
mailing list