Home > Net >  Why is it when I return a `double` from a `void *` function, it becomes incompatible?
Why is it when I return a `double` from a `void *` function, it becomes incompatible?

Time:09-30

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.

  • Related