-2

To take an example, lets say I would like to write a simple procedure that deletes the 'X' characters from a string.

How can I design my procedure so that it works for both string and PCHAR parameters.

If I define it as:

procedure RemoveX( source : PCHAR);

than calls to RemoveX(PCHAR(mystring)) where myString is a string will remove the 'X' but will not take care of updating the string length ... Hence a subsequent myString := myString + 'done' will leave myString unchanged. And I don't want to change the length after the call to RemoveX, I expect the RemoveX procedure to deal with everything.

If on the other hand I define it as:

procedure RemoveX( var source : string);

I don't know how to pass it a PCHAR ...

  • As per Remy's answer, you should be able to see that trying to write a single universal function for different types leads to complications. This is the point of the **overload** directive. It allows you to write separate functions, each tailored to the specific type it wants to handle. The benefit is that the function name remains the same, so users don't have to remember different names depending on the which type they want to pass in. (NOTE: This applies in any situation where you have different types that can be used in a similar way. - Not only string/PChar) – Disillusioned Nov 06 '14 at 08:55
  • I am aware of the overload concept. But you are missing the point. For a function that does not modify the string length, you only need the PCHAR version and you call it with strings casted to PCHARs. So would you really provide overloads for all string functions? I guess not. – Emmanuel Ichbiah Nov 07 '14 at 08:17
  • No, it seems to me you're missing the point. `PChar` and **string** have fundamental differences. Particularly how you determine the end of the string. And the issue is not about _modifying_ the length. It's about operating within the confines of allocated memory. (_Have you heard about buffer overrun errors?_) And expecting callers of your function to pass in the length is really klunky. (_WinAPI style that Delphi strings successfully avoid._) As for whether ***I*** would provide overloads for all string functions? If I need both versions, YES! But I seldom need `PChar` at all. – Disillusioned Nov 07 '14 at 15:16

2 Answers2

5

I would not suggest implementing the string version in terms of the PChar version, or vice versa. I would keep them separate so that you can tailor them independently, eg:

procedure RemoveX(source : PChar); overload;
procedure RemoveX(var source : string); overload;

procedure RemoveX(source : PChar);
var
  P: PChar;
  Len: Integer;
begin
  if source = nil then Exit;
  Len := StrLen(source);
  repeat
    P := StrScan(source, 'X');
    if P = nil then Exit;
    StrMove(P, P+1, Len - Integer(P-source));
    Dec(Len);
    source := P;
  until False;
end;

procedure RemoveX(var source : string);
begin
  source := StringReplace(source, 'X', '', [rfReplaceAll]);
end;

Update: If you really want to use a single implementation for both PChar and String inputs then you can do something like this:

function RemoveX(source : PChar; sourceLen: Integer): Integer; overload;
procedure RemoveX(source : PChar); overload;
procedure RemoveX(var source : string); overload;

function RemoveX(source : PChar; sourceLen: Integer): Integer;
var
  P: PChar;
begin
  Result := 0;
  if (source = nil) or (sourceLen = 0) then Exit;
  repeat
    P := StrScan(source, 'X');
    if P = nil then Exit;
    StrMove(P, P+1, sourceLen - Integer(P-source));
    Dec(sourceLen);
    source := P;
  until False;
  Result := sourceLen;
end;

procedure RemoveX(source : PChar);
begin
  RemoveX(source, StrLen(source));
end;

procedure RemoveX(var source : string);
begin
  UniqueString(source);
  SetLength(source, RemoveX(PChar(source), Length(source)));
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks Remy. It makes sense indeed. My intitial hope was to have a single routine that would work for both. For routines that do not modify the string length it is easily doable. But when there is an impact on the string length, it seems impossible, strangely. – Emmanuel Ichbiah Nov 05 '14 at 20:44
  • @Emmanuel I assumed you wanted to avoid implementing the function twice. Did I misunderstand? – David Heffernan Nov 05 '14 at 21:06
  • @Remy Your update is no good for the final overload unless the ref count of the string is 1. – David Heffernan Nov 05 '14 at 22:46
  • @David while your suggestion is perfectly OK, my hope was to have 1 function, not 2 overloads. For functions that dont impact the string length, all you need to do is provide a Change(source:PCHAR) function and you can call it with a string parameter by casting it to PCHAR. When the string length is impacted this is not possible anymore. It is then dangerous to call Change( PCHAR(mystring)) because the length of the string will not be updated correctly internally. Even with your two overloads, you run the danger that a guy makes a pervers RemoveX(PCHAR(mystring)) call. Do you see my point? – Emmanuel Ichbiah Nov 07 '14 at 08:14
  • I already told you that you cannot do what you ask with one single function. I believe that to be the case. It sounds as though you don't believe that. I don't know what you are looking for any more. – David Heffernan Nov 07 '14 at 08:23
  • @EmmanuelIchbiah As examples have shown, it is not safe to just pass a `PChar(string)` to a `procedure(souce: PChar)` without ensuring the `string` is unique first. Imagine what happens if you pass a string literal, or a string with `RefCnt > 1`, to a procedure that does not alter the *length* of the `PChar` but does alter the *content* of the `PChar`. Altering a string literal will produce a runtime crash, and altering a `string` with `RefCnt > 1` will break string variables other than the one you passed in. So you need an overload to handle `string` differently. – Remy Lebeau Nov 07 '14 at 17:49
  • 1
    @EmmanuelIchbiah: if you want people to be able to pass in either `PChar` or `String`, you are best off providing overloads for them. It is better to have smaller specialized functions than to have fewer general functions. It makes code management and debugging easier. – Remy Lebeau Nov 07 '14 at 17:52
  • @EmmanuelIchbiah Please listen to the advice of Remy, David and myself. There is nothing wrong with implementing 2 separate *type-specific* routines to do the same thing. It is not a violation of the DRY principle (if that's your concern). The differences between `PChar` and **string** make it impossible to implement a 'universal function' safely without imposing more work on users of your function. Your effort would be spent much better elsewhere. – Disillusioned Nov 10 '14 at 11:04
2

You cannot implement this using a single parameter. You have two different types.

You could build the string version on top of a PChar version.

procedure RemoveX(var str: string);
var
  P: PChar;
begin
  UniqueString(str);
  P := PChar(str);
  RemoveX(P);
  str := P;
end;

An alternative for final line could be:

SetLength(str, StrLen(P));

Either way, this obviously assumes that you already have a functioning overload that operates on PChar. And that the function removes characters. Clearly it cannot extend the PChar buffer.

The call to UniqueString is needed in case the string is shared (ref count greater than one) or constant. After this call the string buffer is editable and not shared.

Whether or not avoiding duplication of implementation in this way is the best approach I cannot say. It depends on your design drivers. If simplicity and clarity of code is key, then avoiding duplication makes sense. If performance is key then it may be desirable to provide two bespoke implementations.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490