abelenky
abelenky

Reputation: 64682

Problem using `_Generic` with type-promotion

When calling a C-function its OK to call with a wider type by passing a narrower type that can be converted/promoted into the wider type.

For example:

void FooBar(uint32_t alpha);  // Function takes a 32-bit unsigned value

int main(void)
{
    uint16_t foo = 232;  // Variable foo is 16-bit unsigned
    FooBar(foo);         // 16-bit value gets auto-promoted to 32-bit value for the function call.
}

However, when I try to do this with _Generic in C11, auto-promotion seems to be lost:

#define FooBar(x) _Generic((x), uint32_t: FooBar_U32,\
                                double:   FooBar_DBL)(x)

int main(void)
{
    uint16_t foo = 232;  // Variable foo is 16-bit unsigned
    FooBar(foo);         // Try to promote a 16-bit to 32-bit value for FooBar_U32

    float nib = 3.14;
    FooBar(nib);  // Try to promote a float to a double for FooBar_DBL
}

The error is:

prog.c:7:28: error: ‘_Generic’ selector of type ‘short unsigned int’ is not compatible with any association

I would certainly expect a short unsigned int to be compatible with a unsigned int (32-bit). So I do not understand the error message.

Is it impossible to use _Generic and get the benefits of type-promotion?
Am I doing something wrong here?

(Sample Code in IDEOne)

Upvotes: 0

Views: 114

Answers (1)

John Bollinger
John Bollinger

Reputation: 180306

This is "compatible" in the sense defined by the language specification, not in the much broader sense of pairs of types between which automatic conversions are defined, or where the integer promotions apply. Relevant bits are spread across several sections of the specification, including at least sections 6.2.7 ("Compatible type and composite type"), 6.7.2 ("Type specifiers"), 6.7.3 ("Type qualifiers"), and 6.7.6 ("Declarators"). For arithmetic types other than enumerated types, however, "compatible type" simply boils down to the same type, including type qualifiers.

That is indeed very strict. More so, for example, than is required by the strict aliasing rule for accessing an object of one type via an lvalue of a different type. Much more so than is required for type matching across assignments or for matching function arguments to corresponding parameters. And no, short unsigned int is not compatible with unsigned int in this sense, not even in implementations where the two types are the same size.

Is it impossible to use _Generic and get the benefits of type-promotion?

You get lvalue conversion, which drops type qualifiers, but generic associations are not among the places where the "usual arithmetic conversions" apply, nor do the automatic conversions performed in the context of assignments and function calls apply in this context (these are explicitly called out in the language spec where they apply).

This fine-grained approach is intentional. The point is for generic associations to be able to distinguish between cases that are, among other things, assignment-compatible. For example,

#define hton(x) _Generic((x), uint32_t: htonl, uint16_t: htons)(x)

Am I doing something wrong here?

Your code does not conform to the language spec. You can fix it by defining a generic association for each relevant type, and / or by converting the type of the operand of the generic selection to one of the types for which an association is provided.

One of the ways to do that would be to alter the expression in your generic selection to one that engages the usual arithmetic conversions (as opposed to the generic selection itself doing so), as was described in comments on the question. But do note that the usual arithmetic conversions are narrower than the assignment conversions. Also, that might not be useful in your particular case, because the the integer promotions of uint32_t and uint16_t (as engaged as part of the usual arithmetic conversions) are not necessarily compatible. It is reasonably likely the the former is unsigned int whereas the latter is int.

Upvotes: 2

Related Questions