Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function Pointers with different signatures (example: optional parameter with a default value)

Is it possible to create a function-pointer with a default parameter, something like

TFunctionPointer = function(sName:AnsiString; tOptional: TObject = nil):smallint;

What I want to achieve:

A function pointer, which can accept a function of type

function A(sName:AnsiString)

or

function B(sName:AnsiString, tOptional: TObject)

How can I achieve this?

like image 546
skylla Avatar asked Nov 06 '13 08:11

skylla


2 Answers

Default parameter is just a syntactic sugar - actually function call has two parameters.

But you can use function references and anonymous methods to create such function pointers - function adapters.

type
  fnA = function(const sName: AnsiString): integer;
  fnB = function(const sName: AnsiString; const tOptional: TObject); integer;

  fnRef = reference to function(const sName: AnsiString; const tOptional: TObject): integer;

  fnBridge = record 
     Bridge: fnRef;
     class operator Implicit(fn: fnA): fnBridge;
     class operator Implicit(fn: fnB): fnBridge;
  end;

class operator fnBridge.Implicit(fn: fnA): fnBridge;
begin
  Result.Bridge := 
     function(const sName: AnsiString; const tOptional: TObject): integer
     begin
       Result := fn(sName); 
     end;
end;

class operator fnBridge.Implicit(fn: fnB): fnBridge;
begin
  Result.Bridge := 
     function(const sName: AnsiString; const tOptional: TObject): integer
     begin
       Result := fn(sName, tOptional); 
     end;
end;

function A(const sName: AnsiString): integer; 
begin Result := Length(sName) end;

function B(const sName: AnsiString; const tOptional: TObject): integer; 
begin Result := Length(sName) - Length(tOptional.ClassName) end;

function Consumer (const Param1, Param2: integer; const Action: fnBridge): integer;
begin
  Result := Param1 + Param2 * Action.Bridge('ABCDE', Application);
end;

....
  ShowMessage( IntToStr( Consumer(10, 20, A) ));
  ShowMessage( IntToStr( Consumer(10, 20, B) ));

PS: since Delphi version was not specified it means that answer for ANY Delphi version suits fine. This method should work wit hany version starting with Delphi 2009 and later.

PPS: references to functions with captured variables are implemented internally as TInterfacedObject descendants. So overall this is just a reduced case of "Strategy pattern" using "higher-order functions"

  • http://en.wikipedia.org/wiki/Strategy_pattern
  • http://en.wikipedia.org/wiki/Higher-order_function
like image 95
Arioch 'The Avatar answered Sep 19 '22 22:09

Arioch 'The


That is not possible. In order for a function to be of type TFunctionPointer, it must declare two parameters.

A default parameter is still a parameter. Your TFunctionPointer is a function with two parameters. When you call it and supply only one parameter, the compiler supplies the default parameter at the call site. So two parameters are still passed to the function.

To expand on this. Consider the following:

procedure Foo(Bar: Integer=666);
begin
end;

When you call the procedure like this:

Foo();

it looks as though the procedure has no parameters. But that is not the case. The compiler translates your code into this:

Foo(666);

The conclusion is that if you want to allow receipt of functions with different numbers of parameters, you'll need to provide an explicit mechanism to receive those different function types. For instance:

procedure DoSomething(const Callback: TProc<string, TObject>); overload;
begin
  Callback(str, obj);
end;

procedure DoSomething(const Callback: TProc<string>); overload;
begin
  DoSomething(
    procedure(arg1: string; arg2: TObject)
    begin
      Callback(arg1);
    end
  );
end;
like image 30
David Heffernan Avatar answered Sep 19 '22 22:09

David Heffernan