I created 2 functions, to read and write to a path, declared as such:
int Read(const char * /*Filename*/, void * /*Ptr*/, size_t /*Size*/), Write(const char * /*Filename*/, const void * /*Ptr*/, size_t /*Size*/);
I created an additional function to that will call one of the above functions with a path
static int IOData(int(*const Func)(const char *, void *, size_t)) {
char Filename[DATA_PATH_LEN];
// Build path
return Func(Filename, &Data, sizeof(Data));
}
However, when Write
is passed as a callback to IOData
, the compiler raises the following warning
Incompatible pointer types passing 'int (const char *, const void , int)' to parameter of type 'int ()(const char *, void *, int)'
Would casting a function that accepts a const pointer to a function that accepts a non const pointer be undefined behavior?
I noticed that there is an almost identical question but that question uses C but this question uses plain C so using templates is not an option
CodePudding user response:
This is not allowed because the types of one of the corresponding parameters is not compatible.
Compatible types are defined in section 6.2.7p1 of the C standard:
Two types have compatible type if their types are the same. Additional rules for determining whether two types are compatible are described in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in 6.7.6 for declarators. ...
And section 6.7.3p10 details compatibility of qualified types:
For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.
This means that const void *
and void *
are not compatible.
Compatibility of function types is described in section 6.7.6.3p15:
For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)
So because one set of corresponding parameters are not compatible, the function types are not compatible.
Finally, section 6.5.2.2p9 regarding the function call operator ()
describes what happens in this case:
If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.
So calling a function through an incompatible function pointer type triggers undefined behavior and therefore should not be done.
CodePudding user response:
The Standard seeks to classify as Undefined Behavior any action whose behavior might be impractical to define on some plausible implementations. Because classifying an action as UB would in no way impairs the ability of a quality implementation to, as a "conforming language extension", process the action in a useful commonplace fashion when one exists, there is no need to avoid characterizing as UB actions which most implementations would process in the same useful fashion.
An implementation that attempts to statically determine maximum stack usage might plausibly assume that calls to a function pointer with a particular signature will only invoke functions whose address is taken and whose signature matches perfectly. If the Standard were to require that pointers to such functions be interchangeable, that might irredeemably break programs which the static analysis tools had previously been able to accommodate.
There's no reason to expect that quality implementations shouldn't be configurable to treat such function pointers as interchangeable in cases where there doing so would be useful and practical, but the Standard waives jurisdiction over quality-of-implementation issues of usefulness and practicality. Unfortunately, it can be hard to know which implementations should be relied upon to support such constructs, because many implementations which would have no reason not to support such constructs don't regard the fact that they support them as being sufficiently noteworthy as to justify explicit documentation.