Search code examples
cgenerics

Unexpected Behavior in C _Generic with Compound Literals


I've encountered unexpected behavior when using _Generic in C with compound literals. It appears that _Generic picks the first case that contains a compound literal, regardless of whether it matches the actual type of the expression. This happens even though other cases should match more precisely.

Consider the following code:

typedef struct { unsigned char _dummy_m[3]; } int24_t;

#define GET_VALUE_ADDRESS(v) _Generic((v), \
    int*:           &(v), \
    double:         &(double){v}, \
    int24_t:        &(v), \
    default:        &(v))

int main (void)
{
    int24_t val = {1,2,3};
    void* addr = GET_VALUE_ADDRESS(val); /* erroneous */
    return 0;
}

The compiler is MinGW-GCC Rev2, Built by MSYS2 project 10.3.0 and with the following CMake configuration:

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_EXTENSIONS ON)

The code produces the following error:

incompatible types when initializing type 'double' using type 'int24_t'

Expected Behavior:

In this example, v is of type int24_t, so I expect _Generic to match the int24_t case and return &(v).

Actual Behavior:

Instead, the compiler seems to always pick the first case that involves a compound literal (in this case, &(double){v}), regardless of the type of v. This results in incorrect type matching and an unexpected output.

Additional Information:

If I remove the compound literal(s) and replace them with regular address-of operations, _Generic works as expected. The issue only arises when using compound literals as the return value in _Generic.

Question: Even if this is not a bug or quirk in _Generic itself, still the error I am getting is nothing short of unhelpful. Is this a known bug or a limitation in how _Generic handles compound literals? If so, is there any workaround, or am I misusing _Generic in this context?


Edit

I don't know why I haven't thought of that immediately, but the solution is quite simple with compound expressions and typeof:

#define addressof(v) ({ __typeof__(v) _tmp=v; &_tmp; })

Of course, this is reliant on extensions.


Solution

  • It appears that _Generic picks the first case that contains a compound literal…

    _Generic has not picked any case here, or, more precisely, the compiler has not resolved the _Generic expression to any of the operands. The error message comes from analyzing the entire _Generic expression, as explained below, not from any resolution of which expression will be used.

    incompatible types when initializing type 'double' using type 'int24_t'

    _Generic is not a preprocessor construct that conditionally chooses some source code, like #if does. It is an expression in C, and all of the expressions inside its list must be proper expressions (specifically, each must be an assignment-expression in the formal grammar). When v is val, &(double){v} is &(double){val}. This expression is analyzed by the compiler, and the compound literal in it attempts to initialize a double using val. val is an int24_t, which you have defined to be a structure. The compiler correctly reports an error attempting to initialize a double with a structure.

    The fact that this list item will later not be selected by the _Generic is irrelevant; the compiler has to fully analyze each list item first. This item fails analysis.

    Is this a known bug or a limitation in how _Generic handles compound literals?

    No, this is correct behavior by the compiler and is an actual error in your source code.

    If so, is there any workaround, or am I misusing _Generic in this context?

    In the use case you show, the macro could be defined simply as #define GET_VALUE_ADDRESS(v) (&(v)), or you could simply assign the address without a macro, as in void *addr = &val;. However, based on the macro name, I suspect you want to create an address given a non-lvalue value of arbitrary type, or at least of several possible types. You can do this with the GCC and Clang extension typeof, which is expected to be in the forthcoming C standard:

    #define GET_VALUE_ADDRESS(v) ((typeof (v)[]) { v })
    

    This forms a compound literal that is an array of the desired type. When the array is used in an expression other than as the operand of sizeof or unary &, it will be automatically converted to a pointer to its first element, providing the address you want.