5

Delphi 2010 - I have a routine which takes a string and processes it. There are 3 different types of processing, and I may need any combination, including all 3 ways of processing. I am trying to determine how to call my routine, but everything I try is causing issues. What I want to do is call the procedure something like this...

StringProcess(StartString1, VarProcess1, VarProcess2, VarProcess3);

but it could just as easily be this is I only want 2 of the processes

StringProcess(StartString1, '', VarProcess2, VarProcess3);

The procedure definition is something like

procedure StringProcess(StartString: string; var S1:String; var S2:string; var S3:string);

So in summary... How do I define my procedure to return between 1 and 3 VAR variables? Delphi is wanting me to always pass 3 variables, and I just have to ignore the one if I don't need it. Is there a way to pass "non-existant" VAR parameters, and just ignore them as needed?

Thanks

user1009073
  • 3,160
  • 7
  • 40
  • 82
  • @lurker That question might suggest a good alternative to this, but it's certainly not a duplicate. – GolezTrol May 22 '14 at 14:10
  • 1
    This question is about a function with 3 **var** string inputs, asking how to make one of them optional. The other question is about an `array of const` input, asking how to make it a var parameter. That sounds different to me. – GolezTrol May 22 '14 at 14:14
  • @GolezTrol OK sorry I probably grabbed the wrong SO answer to cite here. The title of the cited question I used was *How to Create a function which accepts variable number of variable arguments*. I've retracted my close vote. – lurker May 22 '14 at 14:19

3 Answers3

11

A var parameter cannot be optional, it must be passed a variable. For what you are looking for, use pointers instead:

procedure StringProcess(StartString: string; S1:PString; S2:Pstring; S3:Pstring);
begin
  ...
  if S1 <> nil then
  begin
    // Use S1^ as needed...
  end;
  ...
end;

Then you can do things like this:

StringProcess(StartString1, @VarProcess1, @VarProcess2, @VarProcess3);
StringProcess(StartString1, nil, @VarProcess2, @VarProcess3);
StringProcess(StartString1, nil, nil, @VarProcess3);
StringProcess(StartString1, @VarProcess1, nil, @VarProcess3);
...
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • The pointer stuff doesn't look very nice, but at least this solution allows you to detect whether the caller actually passed a string or nil, so you can check whether you need to process that string or not. – GolezTrol May 22 '14 at 15:16
  • 1
    The other way around is also possible, `StringProcess(StartString1, PString(nil)^, VarProcess2, ..` and then test for `@S1<>nil` in the proc. Declaration of the procedure remains the same, but it doesn't look any nicer ... – Sertac Akyuz May 22 '14 at 18:27
9

The issue is that your StringProcess rountine must have variables to represent S1, S2 and S3 whenever it tries to modify these values. No doubt you don't want to go through the headache of declaring variables for values you're not interested in.

One option to consider is bundling all the variables into a record structure as follows:

type
  TStringData = record
    S1, S2, S3: string;
  end;

procedure StringProcess(StartString: string; var StringData: TStringData);

However I'd go a step further. I suspect you aren't really using inputs of S1, S2 and S3 in your StringProcess routine. Which means they may as well be out parameters. In which case I'd rather suggest you write:

function StringProcess(StartString: string): TStringData;
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • Just as easy, but in this case there is still no view on which strings you want to have. That means all of them are calculated by the procedure, which might be a waste of cycles (depending on the complexity of the processing). – GolezTrol May 22 '14 at 15:13
  • 1
    @GolezTrol `if (S1 = '')` and `if (StringData.S1 = '')` can be interpreted in exactly the same way if there is an intention to use input values to decide what should be calculated. And since strings are managed types, and local record variable will automatically be initialised to all empty strings. – Disillusioned May 22 '14 at 15:20
1

var parameters cannot be optional. A solution would be to define overloads for the function:

procedure StringProcess(StartString: string; var S1:String); overload;
procedure StringProcess(StartString: string; var S1:String; var S2:string); overload;
procedure StringProcess(StartString: string; var S1:String; var S2:string; var S3:string); overload;

You can implement the overloads like this:

procedure StringProcess(StartString: string; var S1:String); overload;
var
  S2, S3: String;
begin
  StringProcess(StartString, S1, S2, S3);
end;

The overload with one process just call the overload with the most processes and uses dummy variables to capture the output.

GolezTrol
  • 114,394
  • 18
  • 182
  • 210
  • This would NOT work because if I am passing two strings, the routine would not know if I am passing S1 and S3, or S2 and S3 etc...It just knows I am passing 2 strings. I would have to add another parameter that tells the procedure which one of the 7 combinations I want. – user1009073 May 22 '14 at 14:13
  • Clearly the compiler has to know what you intend. You cannot expect it to read your mind. – David Heffernan May 22 '14 at 14:18
  • Okay. I see what you mean now. The quickfix would be to just pass 3 variables and a bitmask that tells the function which of them you want to actually have. But for a nicer solution I would choose a completely different approach, because this one isn't flexible at all. Isolate the different kinds of processing in different classes or so. Anyway, the core of the answer is: You cannot have optional var parameter, so you'll have to work around that. – GolezTrol May 22 '14 at 14:21
  • @David - When I try to pass '' as a parameter, I get an error telling me that I cannot use a CONST as a VAR variable. This makes sense, but is there a way to tell the compiler that is there is NO variable as input, then don't pass any variable back? – user1009073 May 22 '14 at 14:22
  • @Golez - The reason I was putting everything in one procedure is that so much of the processing is Identical. If I do the 1st type of processing, doing 2nd type is only 10% more wok, etc. – user1009073 May 22 '14 at 14:24
  • 2
    I think a class would be the ideal solution for this. Set the input string in the class, and make functions or property getters to get the different processed strings. Each of those functions can check if the shared part of the processing is already done. If not, it can call the method that does that part, and store the results in class fields. – GolezTrol May 22 '14 at 15:12
  • 2
    @GolezTrol Agreed. People are often scared of creating small classes. But this could very well be an ideal example of a **Single Responsibility** class. – Disillusioned May 22 '14 at 15:23