(Note this is a language-lawyer question.)
Deprecated Question
Update: I botched Question 0. When I wrote this, I was looking at the parameter-argument type rules in C 2018 6.5.2.2 6, which are not in a Constraints section and so may be neglected by a compiler. I overlooked 6.5.2.2 2, which is in a Constraints section, so a compiler is required to diagnose mismatched types. I would not have asked Question 0 if I had noticed this.
In this question, we desire code such as:
int AddVersion0(int a, int b ) { return a+b; }
int AddVersion1(int a, int b, int c) { return a+b+c; }
typedef int (*TypeVersion0)(int, int);
typedef int (*TypeVersion1)(int, int, int);
#define Foo(f, a, b) _Generic((f), \
TypeVersion0: (f)((a), (b)), \
TypeVersion1: (f)((a), (b), 0) \
)
#include <stdio.h>
int main(void)
{
printf("%d\n", Foo(AddVersion0, 3, 4));
printf("%d\n", Foo(AddVersion1, 3, 4));
}
(Foo has been parameterized with a function f to facilitate demonstration and analysis.
In the original context, this is not needed.)
With default switches, optionally adding -std=c18, both GCC 10.2 and
Apple Clang 11.0 reject this code complaining as an error, not a warning, there are too many arguments to one function call (AddVersion0 in the second case of the first use of Foo) and too few to another (AddVersion1 in the first case of the second).
_Generic is processed because _Generic is defined to produce a “result expression,” not a “result value,” in C 2018 6.5.1.1 3. GCC and Clang are applying run-time constraints on a function call to a function call that does not become part of the program. 6.5.1.1 3 includes:
If a generic selection has a generic association with a type name that is compatible with the type of the controlling expression, then the result expression of the generic selection is the expression in that generic association.
Question 1
Next, consider this workaround:
int AddVersion0(int a, int b ) { return a+b; }
int AddVersion1(int a, int b, int c) { return a+b+c; }
typedef int (*TypeVersion0)(int, int);
typedef int (*TypeVersion1)(int, int, int);
int NeverCalled();
#define Sanitize(Type, f) _Generic((f), Type: (f), default: NeverCalled)
#define Foo(f, a, b) _Generic((f), \
TypeVersion0: Sanitize(TypeVersion0, (f))((a), (b)), \
TypeVersion1: Sanitize(TypeVersion1, (f))((a), (b), 0) \
)
#include <stdio.h>
int main(void)
{
printf("%d\n", Foo(AddVersion0, 3, 4));
printf("%d\n", Foo(AddVersion1, 3, 4));
}
Neither GCC 10.2 nor Apple Clang 11.0 complain about this.
Question 1: Could a compiler have grounds to complain about this? As NeverCalled is not declared with a prototype, C 2018 6.5.2.2 6 does not say any call has undefined behavior unless the function is defined with a type that does not include a prototype and the argument types do not match the parameter types. But the function is not defined at all, so that condition is
not triggered.
(I ask whether the compiler has grounds for complaint because of course a compiler is permitted to complain about anything, as a warning that does not prevent compiling the program, but the question is whether a compiler could deduce that some aspect of this code violates some aspect of the C standard.)