Laszlo wrote:
Can you distinguish between omitted parameters and ones, which happen to be their defaults?
From my testing, no (for non-ByRef parameters). For ByValue (non-ByRef) parameters, a copy of the variables contents are copied. This is "exactly" what is done when a default value is used (in the case of omitting the parameter). I haven't checked the source code, so "exactly" means "as reflected in my testing."
Code:
a := "1"
Function(a)
Function(a, 2)
Function(1, 2)
Function(arg1, arg2 = 2)
{
;these addresses remain the same for all calls
MsgBox, % (&arg1) " " (&arg2)
}
Now, ByRef, optional parameters you CAN check. Because ByRef parameters pass an address, instead of copying the variable's contents, you can test whether the address for the variable is the "local version" or not. If the parameter is passed, the address is that of the passed value. If omitted, it is the address of the local variable.
In v1.0.46.13+, ByRef parameters also support default values; for example: Func(ByRef p1 = ""). Whenever the caller omits such a parameter, the function creates a local variable to contain the default value; in other words, the function behaves as though the keyword "ByRef" is absent.
However, to use this method, you need to "cache" the address and compare it. Since the address for the parameters don't change between calls (from my testing, at least), you can use this to compare the address in the "cache" with the passed address. If they are identical, the local copy is used (i.e. the parameter was omitted). Otherwise, the parameter was passed.
The caching requires calling the function, ommitting the optional arguments. This would be the "first run". By using a static variable, you can check if the run is the "first run". If it is, you cache the value and return (don't actually run the function, in full). Otherwise, first use the cache to determine if the ByRef parameter was passed.
Code:
;first run (builds the address "cache")
;mandatory parameters can be anything,
; since the function is returned immediately after building the "cache"
Function(1)
Function2(1)
arg2 := 2
arg3 := 3
arg4 := 4
;passed one parameter
Function(1)
;passed two parameters
Function(1, arg2)
;passed three parameters
Function(1, arg2, arg3)
;passed one parameter
Function2(1)
;passed two parameters
Function2(1, arg2)
;passed three parameters
Function2(1, arg2, arg3)
;passed four parameters
Function2(1, arg2, arg3, arg4)
Function(arg1, ByRef arg2 = 2, ByRef arg3 = 3)
{
;alreadyDone tracks the "first run",
;paramCount is the total number of parameters (including optional ones)
static alreadyDone, ParamCount = 3
if (!alreadyDone)
{
;"first run"
alreadyDone := true
;set-up the "cache" for the addresses of the ByRef variables
A_ParamCount(A_ThisFunc, ParamCount, arg2, arg3)
;don't actually run the function - just build the "cache"
return
}
else
{
;not the "first run" - see which parameters, if any, were omitted
;specify the negative of ParamCount to
A_ParamCount := A_ParamCount(A_ThisFunc, -ParamCount, arg2, arg3)
}
MsgBox, % "A_ParamCount = " A_ParamCount
}
Function2(arg1, ByRef arg2 = 2, ByRef arg3 = 3, ByRef arg4 = 4)
{
;alreadyDone tracks the "first run",
;paramCount is the total number of parameters (including optional ones)
static alreadyDone, ParamCount = 4
if (!alreadyDone)
{
;"first run"
alreadyDone := true
;set-up the "cache" for the addresses of the ByRef variables
A_ParamCount(A_ThisFunc, ParamCount, arg2, arg3, arg4)
;don't actually run the function - just build the "cache"
return
}
else
{
;not the "first run" - see which parameters, if any, were omitted
;specify the negative of ParamCount to
A_ParamCount := A_ParamCount(A_ThisFunc, -ParamCount, arg2, arg3, arg4)
}
MsgBox, % "A_ParamCount = " A_ParamCount
}
/*
used to cache the addresses of optional ByRef parameters to detect if they were passed or omitted
FunctionName is the function's name (A_ThisFunc)
ParamCount is the total number of parameters (including optional ones)
arg# is the 1st, 2nd, 3rd, ... ByRef argument
if ParamCount is negative, then (-ParamCount) is the
actual number of parameters (including optional ones),
and the ACTUAL number of parameters passed will be returned
Note: using this method works only if all optional parameters are ByRef
Other means are required to detect non-ByRef parameters
*/
A_ParamCount(FunctionName, ParamCount = "", ByRef arg1 = "", ByRef arg2 = "", ByRef arg3 = "", ByRef arg4 = "", ByRef arg5 = "", ByRef arg6 = "", ByRef arg7 = "", ByRef arg8 = "", ByRef arg9 = "")
{
static
if (ParamCount < 0)
{
;return the actual parameter count
ParamCount *= -1
;for each parameter
loop, % ParamCount - (IsFunc(FunctionName) - 1)
{
;if the address equals the cached address, then it was omitted
if (&arg%A_Index% = @$%FunctionName%#%A_Index%)
{
;IsFunc(FunctionName) - 1 is the number of mandatory parameters
;A_Index - 1 is to refer to the parameter before this one
; (because it was omitted)
return IsFunc(FunctionName) - 1 + A_Index - 1
}
}
;all arguments were passed
return ParamCount
}
;for each parameter
loop, % ParamCount - (IsFunc(FunctionName) - 1)
{
;store the variable's address
@$%FunctionName%#%A_Index% := &arg%A_Index%
}
}
This work around works for any function that takes ONLY ByRef arguments for the optional parameters. If a way to detect an omitted non-ByRef parameter versus one passed with the default value becomes known, it can be added to the above function, and we can emulate an A_ParamCount value.
Please don't start using the above workaround yet, as I'm going to start some more testing. For example, it might be possible to detect more cases than it does, for example, functions that have some ByRef and some ByValue parameters for optional parameters. Also, if a value isn't the default value, we know it was passed. So, I'm hoping that I can expand the above function to all cases except ByValue parameters whose passed argument is the default value.
I'll post the updates here.