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.
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.