'What, if any, is a technical reason to lead with a function pointer argument and then arguments for that function or the other way around?
Perhaps based deeper on VLAs, debuggers, _Geneirc_, some presently proposed feature of the next C, or some other corner of C, the question:
Is there a coding advantage (objective reason) that prefers one function signature over the other when functions pointers are involved?
I have found none.
Typically the order of arguments in a function signature is simply a style choice.
I am looking though for a reason why in a non-(...) function with a function pointer argument an advantage may exist for a certain order.
I could define a function containing a function pointer as
// Function pointer before its arguments a1,b1 it will eventually use
void foo1(void (*fun)(int a0, double b0), int a1, double b1) {
fun(a1, b1);
fun(a1,-b1);
}
or
// Function pointer after the a2,b2 arguments
void foo2(int a2, double b2, void (*fun)(int a0, double b0)) {
fun(a2, b2);
fun(a2, -b2);
}
Researching the standard C library offers 2 opposite examples.
// Function pointer before `context`.
errno_t qsort_s(void *base, rsize_t nmemb, rsize_t size,
int (*compar)(const void *x, const void *y, void *context), void *context);
// Function pointer last.
void (*signal(int sig, void (*func)(int)))(int);
So far, this certainly is a style choice.
Order is important with VLA
I considered VLAs where the arguments before arr2 are needed and somehow the signature of fun2() might be based on row2, col2, arr2 and derive some benefit. This would offer an advantage for the function pointer to trail.
int foo2(int row2, int col2, char arr2[row2][col2], void (*fun2)(TBD_Signature);
But I came up with no useful example.
[Edit] Perhaps another way to look at one aspect of this question:
Can the signature of the function pointer derive from prior arguments of the function in a useful manner?
int bar(int some_arg, other_args,
(*f)(signature based on some_arg, other_args or their type));
Solution 1:[1]
GCC lets you write:
int function1(int arg1, int arg2, int (*function)(int array1[arg1], int array2[arg2]));
which declares a function taking a pointer to function with VLA arguments — but I don't think that pointer to function is usable because the size information isn't available to the actual function, so it has no idea about the size of the two VLAs purportedly passed to it. So you can't write the code for the function to be passed as a pointer.
Now, you could perhaps use:
int function2(int arg1, int arg2, int (*function)(int, int, int array1[arg1], int array2[arg2]));
But that is equivalent to:
int function2(int arg1, int arg2, int (*function)(int, int, int array1[*], int array2[*]));
which you can verify by including both those declarations in a single file and noting that there is no conflict reported. The * notation in subscripts is only allowed in function declarations (not in function definitions).
In the same way, the declaration of function1() above is equivalent to:
int function1(int arg1, int arg2, int (*function)(int array1[*], int array2[*]));
And it isn't really clear that it is different from:
int function1(int arg1, int arg2, int (*function)(int array1[], int array2[]));
All three declarations can coexist in a single source file. The function called via the pointer must have some way to determine the size of the two arrays it is given.
An attempt to use [*] in a function definition yields:
error: ‘[*]’ not allowed in other than function prototype scope
Here's a demonstration of the second function (at work:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int function1(int arg1, int arg2, int (*funcptr)(int array1[arg1], int array2[arg2]));
int function2(int arg1, int arg2, int (*funcptr)(int, int, int array1[arg1], int array2[arg2]));
int function2(int arg1, int arg2, int (*funcptr)(int, int, int array1[*], int array2[*]));
static void dump_array(const char *tag, int n, int array[n])
{
printf("%s (%d):\n", tag, n);
int length = 0;
const char *pad0 = " ";
const char *pad = pad0;
for (int i = 0; i < n; i++)
{
length += printf("%s%d", pad, array[i]);
if (length > 70)
{
length = 0;
pad = pad0;
putchar('\n');
}
else
pad = ", ";
}
if (length > 0)
putchar('\n');
}
static int function(int s1, int s2, int array1[s1], int array2[s2])
{
dump_array("array1", s1, array1);
dump_array("array2", s2, array2);
return s1 + s2;
}
int function2(int arg1, int arg2, int (*funcptr)(int, int, int array1[*], int array2[*]))
{
int a1[arg1];
for (int i = 0; i < arg1; i++)
a1[i] = rand() % 100;
int a2[arg2];
for (int i = 0; i < arg2; i++)
a2[i] = rand() % 100 + 100;
return (*funcptr)(arg1, arg2, a1, a2);
}
int main(void)
{
srand(time(0));
function2(32, 16, function);
return 0;
}
When run, it might produce:
array1 (32):
47, 23, 52, 60, 42, 48, 54, 55, 65, 6, 66, 57, 63, 77, 22, 96, 72, 98
75, 0, 50, 33, 39, 30, 62, 82, 1, 87, 73, 24, 55, 20
array2 (16):
148, 159, 133, 142, 107, 187, 197, 172, 145, 163, 130, 160, 141, 104
156, 165
Solution 2:[2]
You are excluding varargs functions, but I'm not sure why. Also, I'm not sure if you meant to exclude them as an implementation detail, or as a matter of theory.
Regardless, one reason would be for performance. If your calling convention puts the arguments on the stack from right to left, then putting the function-pointer parameter on the left might leave the arguments to the function already on-stack in the correct order:
int call_fn(int (*fn)(void * ignored, A_TYPE a, B_TYPE b), A_TYPE a, B_TYPE b);
In this scenario, the code might simply fetch the function pointer and jump to that address, with the arguments already in the correct location. Alternatively, you might do this to implement a thunk where the virtual table pointer was replaced and then execution continued at a new address.
Solution 3:[3]
I doubt there is any technical reason. Rather, there is a very good semantic reason.
When an object gets conceptually "attached" to another, by way of an assignment or, in the case of function pointers, a call, then we tend to put that object after the one it's attached to. For example, when using a map data structure, you need a function to associate a value with a key: you "attach" that value to the key. That function will almost certainly accept the key parameter first, and the value parameter second. The same kind of order is seen in the language itself when defining a function: the name of the function comes before the names of its parameters, because the parameters are "attachments" to the function, not the other way around.
The qsort_s case is where the function pointer comes first: compar comes before context. Here, the function pointer is a functor, in the sense that it is passed as some kind of object (a pointer) to the main function (qsort_s), which can call it straight away. Its arguments (context) come after, because they are attachments to the functor.
The signal (also sigaction) case is where the function pointer comes last: func comes after sig. Here, the function pointer is a handler, or callback, saved for later use. It isn't called directly; instead, it gets "assigned", or "attached", to some event object (here represented by sig, a signal number), and so comes after it, even if it's then called with that event object as argument. It's like the map function associating a value to a key: the value is the handler, and the key is the event.
In the particular case of qsort_s, compar gets passed values from base as well, but base itself isn't an argument to compar.
This corresponds to a third case where the function pointer comes last but is meant to act on parts of the previous arguments. It often happens in generic algorithms that require some kind of functor, often a predicate, to act on data (the C++ standard library is full of such examples). In this case, the data comes first, even though the function pointer gets called with individual pieces of that data. Conceptually, the functor gets attached to the data to act on it piece by piece.
Plain old qsort (without a context parameter) also qualifies for that case.
As for your last question (Can the signature of the function pointer derive from prior arguments of the function in a useful manner?), I would say no.
It certainly can't change based on the runtime values of the arguments, since the signature is a compile-time thing (at least in C). You could always have void* parameters and change their interpretation based on runtime values, but that's not the same thing.
I don't think it can change based on the types of the other arguments, at least not in C. Other languages have mechanisms that make this possible, but they are usually complicated and not worth it anyway.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | aghast |
| Solution 3 |
