Following on from yesterday’s post, Barry Kelly (CodeGear engineer) kindly clarified a few points, one of which was that Generics support in Delphi 2009 won’t extent to unit procedures, only class methods, so speculation about a possible generic implementation of a Swap()/Exchange() routine was rendered largely academic.
Not to be dissuaded I came up with this solution to meet my current and quite possibly future needs.
Untyped Is Not Enough
I decided that the untyped parameter approach – despite it’s risks – was the one for me, avoiding a library full of overloads. To recap, this is the original untyped parameter implementation:
procedure Exchange(var A, B); var aa: Integer absolute A; bb: Integer absolute B; i: Integer; begin i := aa; aa := bb; bb := i; end;
The problem with this implementation is that it does not support parameters that are not 32-bits in size. This isn’t a problem for the vast majority of cases (my cases – ymmv). It means that it does handle Cardinal, Integer, String (yes, String), object references etc etc. It doesn’t handle Doubles or TDateTime though, for example.
So, I added a refinement so that this routine can safely accomodate parameters of any size, as long as we correctly identify that size when we make the call:
procedure Exchange(var A, B; aSize: Integer); var a8: Byte absolute A; b8: Byte absolute B; a16: Word absolute A; b16: Word absolute B; a32: LongWord absolute A; b32: LongWord absolute B; a64: Int64 absolute A; b64: Int64 absolute B; aE: Extended absolute A; bE: Extended absolute B; i8: Byte; i16: Word; i32: LongWord; i64: Int64; iE: Extended; p: Pointer; begin case aSize of sizeof(Byte) : begin i8 := a8; a8 := b8; b8 := i8; end; sizeof(Word) : begin i16 := a16; a16 := b16; b16 := i16; end; sizeof(LongWord) : begin i32 := a32; a32 := b32; b32 := i32; end; sizeof(Int64) : begin i64 := a64; a64 := b64; b64 := i64; end; sizeof(Extended) : begin iE := aE; aE := bE; bE := iE; end; else GetMem(p, aSize); try CopyMemory(p, Pointer(@A), aSize); CopyMemory(Pointer(@A), Pointer(@B), aSize); CopyMemory(Pointer(@B), p, aSize); finally FreeMem(p); end; end; end;
This is something of a step up in apparent complexity compared to the original – the specific handling of certain parameter sizes is a performance optimisation – the all-purpose CopyMem() approach is some 20x slower than the local variable implementation for 4 byte values, for example.
If anyone out there has some inline ASM that could improve things further, that would be welcome. I can’t seem to figure out the XCHG opcode!
Speaking of 4-byte parameters, as I say, most parameters used with this routine are 4-bytes, so the interface declaration for the routine declares this as the default aSize:
procedure Exchange(var A, B; aSize: Integer = 4);
In use of course things are still very straightforward:
var a, b: Integer; topleft, bottomright: TPoint; s1, s2: String; begin Exchange(a, b); Exchange(s1, s2); Exchange(topleft, bottomright, sizeof(topleft)); end;
Don’t get me wrong – this is far from perfect. The compiler isn’t going to pick you up if you don’t provide aSize when you should or if you provide the wrong size in the aSize parameter although using sizeof() on one of the params, rather than a type name will help avoid that mistake.
Still, the idea of untyped parameters might be off-putting to some people, but bear in mind that FreeAndNIL() accepts an untyped parameter.
But, as with anything I post, no-one’s going to come knocking on your door demanding to know why you aren’t using my code – it’s your choice.
Whether or not you choose to use this routine yourself, quite possibly the techniques used might trigger some insight into some other problem you are facing.
Options For The Future
For this particular problem, the lack of generic support for unit procedures and (as things stand at least) the lack of type inferencing mean that even an implementation based on these new language features are going to have unappealing (to me at least) downsides.
Fixing things to suit this one procedure would be going too far. But maybe it would be useful to have such a routine “built in” to the compiler, perhaps as an operator:
a :=: b;
Which looks kind-a neat, pretty obvious and still “Pascally” to my eye at least.
I doubt I’m the first one to suggest it, and it strikes me that it should be very straightforward to implement. The compiler need only enforce that a and b are simple variables (not properties or function calls, for example) of the same type and generate the necessary inline code
This is the sort of thing that is possibly regarded as so trivial as to not be worth bothering with, even though implementing it would itself also be trivial. But I wonder, would it be something that a lot of Delphi developers would find useful?