I am making a generic programming library, used to make my life a little bit easier when programming in C. The problem I ran across was that when returning a double
from a void *
function, it results in an incompatible types
error. Why is that? I thought void * was supposed to return any datatype, and not just select ones?
main.c
void * _example_code();
int main(void) {
double res = _example_code();
}
void * _example_code() {
return 2.23;
}
CodePudding user response:
The only things automatically convertible to void *
are pointers to object types without qualifiers and null pointer constants, per C 2018 6.5.16.1 1.
double
is not automatically convertible to void *
.
You could return a pointer to double
for a void *
function, but you would need a double
with lifetime extending beyond the function return, and there may be additional issues trying to implement a “generic” function this way. For one thing, when you return a double *
or int *
or other pointer type for a void *
, the calling routine is not informed what type the pointer was before the conversion to void *
. You need to write your program to handle that yourself, by providing some mechanism to inform the caller and have the caller convert the void *
to the appropriate type.
CodePudding user response:
There is no implicit conversion to void*
from double
. Such implicit conversions exist only for object pointer types and null pointer constants.
I wonder what you're expecting that code to do. If there were an implicit conversion, it would most likely take the bits of the double
value 2.23
and interpret them as a pointer value of type void*
(that's how explicit integer-to-pointer conversions typically work). The result would be garbage. In particular, it would not be a pointer to a memory location containing 2.23
.
(The language doesn't say much about the results of integer-to-pointer or pointer-to-integer conversions. There's no guarantee that they just reinterpret the bits; that's just the most common implementation. Avoid converting between integers and pointers until and unless you really need to and know what you're doing. And conversions between pointers and floating-point types are not permitted at all.)
The error message I get for your code is:
c.c: In function ‘main’:
c.c:4:18: error: incompatible types when initializing type ‘double’ using type ‘void *’
4 | double res = _example_code();
| ^~~~~~~~~~~~~
c.c: In function ‘_example_code’:
c.c:8:12: error: incompatible types when returning type ‘double’ but ‘void *’ was expected
8 | return 2.23;
| ^~~~
The error message is actually a bit misleading. It's certainly true that void*
and double
are incompatible, but compatibility is not required here. For example, int
and long
are incompatible types, but you can legally return an int
value from a function returning long
, and the value will be implicitly converted. Type compatibility, as defined by the C standard, is a stronger condition that implicit convertibility. To a first approximation, a type is only compatible with itself (though there are other cases).
I've submitted a gcc bug report for the misleading error message:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107091
clang has a similar issue:
https://github.com/llvm/llvm-project/issues/58066
(Of course the code is still in error.)
CodePudding user response:
C does not have built-in generic containers, void *
is a generic data pointer: you can store the value of a pointer to any data type into it and get back the original pointer by converting it back to the original type.
The effect of storing a double
into a void *
is not defined by the C Standard and your compiler complains that it is an invalid conversion.
For a specified list of types of objects, say int
, double
and const char *
, you can define a generic container this way:
#include <stdio.h>
typedef struct generic {
enum { INT, DOUBLE, STRING } type;
union {
int i;
double d;
const char *s;
} u;
} generic;
generic function1(void);
generic function2(void);
generic function3(void);
int print_generic(const char *s, generic v);
int main() {
generic v1 = function1();
generic v2 = function2();
generic v3 = function3();
print_generic("v1: ", v1);
print_generic("v2: ", v2);
print_generic("v3: ", v3);
return 0;
}
generic function1(void) {
return (generic){ DOUBLE, { .d = 2.23 }};
}
generic function2(void) {
return (generic){ INT, { .i = 42 }};
}
generic function3(void) {
return (generic){ STRING, { .s = "Hello" }};
}
int print_generic(const char *s, generic v) {
switch ( v.type) {
case INT: return printf("%s%d\n", s, v.u.i);
case DOUBLE: return printf("%s%g\n", s, v.u.d);
case STRING: return printf("%s%s\n", s, v.u.s);
default: return printf("%s%s\n", s, "unknown");
}
}
Also note that _example_code
is a reserved identifier. Do not start you identifiers with an underscore, these are reserved for the compiler's internal needs.