We are trying to find a way to get the index of a function argument in C. More specifically, we have a function prototype we want to follow, for example, void f(int a, int b, int *c)
. Unfortunately, this prototype cannot change for compatibility issues. What we want is to allow the user of the function to explicitly state some arguments to be ignored, but because the arguments can be of any kind (if we had only pointer arguments for example, checking the pointers for null values could solve the issue) we are thinking of recording the argument position with some macro and then accessing it from inside the function. We are thinking of something like the following :
f(a, IGNORE(b) or maybe IGNORE, &c)
where IGNORE
somehow records the position of the argument and stores it somewhere (we are going to use some static variables for this) and then we can access the index of the argument to be ignored from inside the function and make choice about ignoring the argument. We know that this can probably be solved by replacing f
with a macro but we are not really sure we are allowed to do this. We are probably allowed to use C for this but again without altering the function prototype. Thanks in advance for your time.
UPDATE : The prototype provided is just an example we also have function with prototype similar to void f(int a, int b)
, so to our knowledge function overloading will not work.
CodePudding user response:
If I understand correctly, you have functions that you cannot change and you want to enable calling the function with "missing" parameters in any position.
You can use overloading by a wrapper with std::optional
arguments. The caller can then either pass parameters or std::nullopt
where the wrapper then uses the default for that argument:
#include <optional>
#include <iostream>
void f(int a,int b){
std::cout << a << " " << b;
}
constexpr int f_a_default = 42;
constexpr int f_b_default = 42;
void f(std::optional<int> a,std::optional<int> b){
f(a.value_or(f_a_default), b.value_or(f_b_default));
}
int main() {
f(1,std::nullopt);
f(2,3);
f(std::nullopt,3);
}
The original f
must not be modified. If the caller does provide parameters for all arguments the original f
is called.
For more complex types you might want to facilitate perfect forwarding.
PS: I assumed you do not want to modify the calls too much, ie f(2, no parameter)
can be f(2,std::nullopt)
but it should not be g(2,std::nullopt)
or g<f>(2,std::nullopt)
. If thats not the case, the whole thing could be made more generic by writing a template wrapper than can be called like this generic_wrapper<f>(2,std::nullopt);
. This would save a lot of boilerplate, because you could use a single wrapper rather than one wrapper for each function.
CodePudding user response:
To not have to pay any runtime cost of checking the optional arguments, you could select the proper overload using tagging.
Example:
#include <iostream>
#include <tuple> // std::ignore
using ignore_type = decltype(std::ignore);
void f(ignore_type, ignore_type, ignore_type) {}
void f(int a, ignore_type, ignore_type) {}
void f(int a, ignore_type, int *c) {
std::cout << "only a and c used\n";
}
void f(int a, int b, ignore_type) {}
void f(ignore_type, int b, int *c) {}
void f(int a, int b, int *c) {
std::cout << "all parameters used\n";
}
int main() {
int var;
f(1, std::ignore, &var); // prints "only a and c used"
}
The above covers all combinations - but you would only implement the combinations that are valid so that the user gets a compilation error if trying an unsupported combination.
CodePudding user response:
I can be achieved in C with a help _Generic
keyword introduce in C11.
This construct allows to select an expression depending on the type of the operand.
The trick is to introduce a dummy Ignore
type which is used to dispatch an expression that will indicate that a given parameter is ignored.
#include <stdio.h>
typedef struct { int _dummy; } Ignore;
_Bool ignore_a;
_Bool ignore_b;
void f(int a, int b) {
if (ignore_a) printf("ignore a");
else printf("a=%d", a);
if (ignore_b) printf(", ignore b\n");
else printf(", b=%d\n", b);
}
#define IGNORE ((Ignore){0})
#define f(a, b) \
f( _Generic(a, Ignore: (ignore_a = 1, 0), default: (ignore_a = 0, (a))), \
_Generic(b, Ignore: (ignore_b = 1, 0), default: (ignore_b = 0, (b))) )
int main() {
f(1, 2);
f(3, IGNORE);
f(IGNORE, 6);
f(IGNORE, IGNORE);
}
It prints as expected:
a=1, b=2
a=3, ignore b
ignore a, b=6
ignore a, ignore b
EDIT
Alternatively one can use the overmentioned schema to select a default value for the ignored argument.
#include <stdio.h>
typedef struct { int _dummy; } Ignore;
void f(int a, int b) {
printf("a=%d b=%d\n", a, b);
}
#define IGNORE ((Ignore){0})
#define f(a, b) \
f( _Generic(a, Ignore: 42, default: (a)), \
_Generic(b, Ignore: 24, default: (b)) )
int main() {
f(1, 2);
f(3, IGNORE);
f(IGNORE, 6);
f(IGNORE, IGNORE);
}
Prints:
a=1 b=2
a=3 b=24
a=42 b=6
a=42 b=24