www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Modify Function Pointer to Take Additional Parameters

reply jmh530 <john.michael.hall gmail.com> writes:
I'm trying to write a function that will adjust the parameters of 
a function pointer.

In the code below, my goal is to call the function qux with a 
variety of different function pointers (in the actual 
application, I don't have the ability to modify qux). I created a 
function foo that I thought would adjust it properly. The problem 
is that the foo function converts the function pointer into a 
delegate.

I was able to get something that works in this simple example by 
introducing a delegate alias and an alternate definition of qux 
that takes a delegate. However, in my actual application, I can't 
modify what the equivalent of qux would take as parameters.

So I was just curious if there was any other alternative.


alias fp1 = int function(int x);
alias fp2 = int function(int x, int y);

auto foo(T)(T f)
{
	static if (is(T == fp2))
		return f;
	else static if (is(T == fp1))
	{
		return (int x, int y) => f(x);
	}
	else
		return 0;
}

int bar(int x)
{
	return x;
}

int baz(int x, int y)
{
	return x + y;
}

int qux(int x, int y, fp2 f)
{
	return f(x, y);
}

void main()
{
	import std.stdio : writeln;

	auto foo_bar = foo(&bar);
	
	writeln(qux(1, 2, foo_bar)); //compiler error
	writeln(qux(1, 2, &baz));
}
Feb 18 2016
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Friday, 19 February 2016 at 05:41:01 UTC, jmh530 wrote:
 I'm trying to write a function that will adjust the parameters 
 of a function pointer.
I think the problem is that it defaults to a delegate not that it cannot be one does clarifying this to the compiler work Like
 alias fp1 = int function(int x);
 alias fp2 = int function(int x, int y);

 auto foo(T)(T f)
 {
 	static if (is(T == fp2))
 		return f;
 	else static if (is(T == fp1))
 	{
 		return int function(int x, int y) => f(x);
 	}
 	else
 		return 0;
 }
?
Feb 19 2016
parent jmh530 <john.michael.hall gmail.com> writes:
On Friday, 19 February 2016 at 11:26:56 UTC, Nicholas Wilson 
wrote:
 Like
 alias fp1 = int function(int x);
 alias fp2 = int function(int x, int y);

 auto foo(T)(T f)
 {
 	static if (is(T == fp2))
 		return f;
 	else static if (is(T == fp1))
 	{
 		return int function(int x, int y) => f(x);
 	}
 	else
 		return 0;
 }
?
This code doesn't compile for me. I have to adjust it to return function int(int x, int y) => f(x); and then it complains that it's a delegate.
Feb 19 2016
prev sibling parent reply Kagamin <spam here.lot> writes:
On Friday, 19 February 2016 at 05:41:01 UTC, jmh530 wrote:
 void main()
 {
 	import std.stdio : writeln;

 	auto foo_bar = foo(&bar);
 	
 	writeln(qux(1, 2, foo_bar)); //compiler error
 	writeln(qux(1, 2, &baz));
 }
int bar(int x) { return x; } int baz(int x, int y) { return bar(x); } void main() { import std.stdio : writeln; int function(int x, int y) foo_bar = &baz; writeln(foo_bar(1, 2)); }
Feb 19 2016
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Friday, 19 February 2016 at 14:21:26 UTC, Kagamin wrote:
 int bar(int x)
 {
 	return x;
 }

 int baz(int x, int y)
 {
 	return bar(x);
 }

 void main()
 {
 	import std.stdio : writeln;

 	int function(int x, int y) foo_bar = &baz;
 	
 	writeln(foo_bar(1, 2));
 }
This works. But when I re-write foo to take that into account as in below, I get an error that I can't implicitly convert int function(int x) to int function(int x, int y). auto foo(T)(T f) { static if (is(T == fp2)) { return f; } else static if (is(T == fp1)) { int function(int x, int y) f_ = f; return f_; } else { return 0; } }
Feb 19 2016
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Friday, 19 February 2016 at 15:00:51 UTC, jmh530 wrote:
 This works.

 But when I re-write foo to take that into account as in below, 
 I get an error that I can't implicitly convert int function(int 
 x) to int function(int x, int y).
I don't think I had looked at what you had done carefully enough. Basically, you just define a new function and take a function pointer of that. That might be a brute force solution. I tried to use a cast (below) to modify the function pointer, but it is printing the second number instead of the first. I find this behavior strange... int foo(int x) { return x; } void main() { import std.stdio : writeln; auto foo_ = cast(int function(int x, int y)) &foo; writeln(foo_(1, 200)); //prints 200 }
Feb 19 2016
next sibling parent reply Yuxuan Shui <yshuiv7 gmail.com> writes:
On Friday, 19 February 2016 at 20:45:23 UTC, jmh530 wrote:
 On Friday, 19 February 2016 at 15:00:51 UTC, jmh530 wrote:
 This works.

 But when I re-write foo to take that into account as in below, 
 I get an error that I can't implicitly convert int 
 function(int x) to int function(int x, int y).
I don't think I had looked at what you had done carefully enough. Basically, you just define a new function and take a function pointer of that. That might be a brute force solution. I tried to use a cast (below) to modify the function pointer, but it is printing the second number instead of the first. I find this behavior strange... int foo(int x) { return x; } void main() { import std.stdio : writeln; auto foo_ = cast(int function(int x, int y)) &foo; writeln(foo_(1, 200)); //prints 200 }
I don't think it's safe to convert between function pointer with different number of arguments... It's possible to mess up the stack frame. Also '(int x, int y)=>f(x)' is clearly a delegate because it refers to local variable 'f', you can't just cast it to 'int function(int, int)'.
Feb 19 2016
parent reply Chris Wright <dhasenan gmail.com> writes:
On Fri, 19 Feb 2016 21:57:46 +0000, Yuxuan Shui wrote:

 I don't think it's safe to convert between function pointer with
 different number of arguments... It's possible to mess up the stack
 frame.
I tested this a fair bit today, and I haven't been able to do any of the nefarious things I expected to be able to do. No overwriting variables in the caller's scope, no smashing stack pointers, etc. I was surprised by this result, but in retrospect, it's relatively obvious. The caller pushes variables onto the stack and sets the stack pointer for the callee. It wouldn't send a stack pointer that pointed into its own stack frame.
Feb 19 2016
parent jmh530 <john.michael.hall gmail.com> writes:
On Friday, 19 February 2016 at 22:34:48 UTC, Chris Wright wrote:
 I tested this a fair bit today, and I haven't been able to do 
 any of the nefarious things I expected to be able to do. No 
 overwriting variables in the caller's scope, no smashing stack 
 pointers, etc.

 I was surprised by this result, but in retrospect, it's 
 relatively obvious. The caller pushes variables onto the stack 
 and sets the stack pointer for the callee. It wouldn't send a 
 stack pointer that pointed into its own stack frame.
Thanks for taking the time to test. The more I've thought about it, the more I wonder if there should be a restriction so that casts of function pointers/delegate maintain the same number of parameters. Even though you haven't been able to do nefarious things, it's giving a completely wrong answer than you would expect. The result of the answer might cause bad things to happen in a program. Further, to even understand what's going wrong you have to understand how the compiler is generating assembly. I've been using D for like a year or so, and I would never have been able to figure out the reason by myself. Or at least in safe code you shouldn't be able to do this.
Feb 19 2016
prev sibling parent reply Chris Wright <dhasenan gmail.com> writes:
On Fri, 19 Feb 2016 20:45:23 +0000, jmh530 wrote:

 I tried to use a cast (below) to modify the function pointer, but it is
 printing the second number instead of the first. I find this behavior
 strange...
If you want to cast function pointers successfully, you have to know the D calling convention. See: https://dlang.org/spec/abi.html Notably: "The last parameter is passed in EAX [a CPU register] rather than being pushed on the stack". So foo expected an argument in EAX, and it dealt with that. Calling foo_ pushes '1' onto the stack, sets EAX to '200', and then jumps to the function address. (But note also: "The callee cleans the stack." So if you pass, say, a struct that has a destructor instead of an integer, that means the struct destructor won't be called. I was a little surprised that the stack pointer is correctly restored.) If you had more arguments, you'd find similar results -- the last argument always goes to EAX, previous arguments are pushed on the stack in order, so you're always ignoring a prefix of the arguments. But it's a byte-wise prefix, so if you change the types, you'll see more significant changes. Casting function pointers is "here be dragons" territory. Unfortunately, it's got the same syntax as routine stuff like integer truncation and class casts, so it looks deceptively safe.
Feb 19 2016
parent jmh530 <john.michael.hall gmail.com> writes:
On Friday, 19 February 2016 at 22:07:25 UTC, Chris Wright wrote:
 If you want to cast function pointers successfully, you have to 
 know the D calling convention.

 [snip]
I figured there was an explanation. Definitely "here be dragons" territory. I hope I can figure out a better solution, but the behavior I'm trying to get is really just a nice to have.
Feb 19 2016