I understand what one would use C11's 'Generic' for, and I would like to use it within a static assertion to guarantee that two user-defined types (typedefs) are the same primative type.
I have made a macro that maps each primative type to an enumerated value, and verified that it works as desired. However, when I try to compare the equality of two resultant macros from two types in a static assert, I get a compiler error. When you comment out the static assert, the code works as expected.
It almost seems as if the static assert is being evaluated by the compiler BEFORE the generic expansion is evaluated. Could this be the case? And where can I go to verify the behavior of this?
Example Code:
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
typedef enum
{
UTIL_TYPE_ENUM_BOOL,
UTIL_TYPE_ENUM_CHAR,
UTIL_TYPE_ENUM_SCHAR,
UTIL_TYPE_ENUM_UCHAR,
UTIL_TYPE_ENUM_SHORT,
UTIL_TYPE_ENUM_USHORT,
UTIL_TYPE_ENUM_INT,
UTIL_TYPE_ENUM_UINT,
UTIL_TYPE_ENUM_LONG,
UTIL_TYPE_ENUM_ULONG,
UTIL_TYPE_ENUM_LONG_LONG,
UTIL_TYPE_ENUM_ULONG_LONG,
UTIL_TYPE_ENUM_FLOAT,
UTIL_TYPE_ENUM_DOUBLE,
UTIL_TYPE_ENUM_LONG_DOUBLE,
UTIL_TYPE_ENUM_OTHER,
} UtilTypeEnum_t;
// returns the enumerated value representing a primitive type
#define UTIL_TYPE_GET_TYPE_ENUM(x) _Generic((x), \
_Bool: UTIL_TYPE_ENUM_BOOL, \
char: UTIL_TYPE_ENUM_CHAR, \
signed char: UTIL_TYPE_ENUM_SCHAR, \
unsigned char: UTIL_TYPE_ENUM_UCHAR, \
short int: UTIL_TYPE_ENUM_SHORT, \
unsigned short int: UTIL_TYPE_ENUM_USHORT, \
int: UTIL_TYPE_ENUM_INT, \
unsigned int: UTIL_TYPE_ENUM_UINT, \
long int: UTIL_TYPE_ENUM_LONG, \
unsigned long int: UTIL_TYPE_ENUM_ULONG, \
long long int: UTIL_TYPE_ENUM_LONG_LONG, \
unsigned long long int: UTIL_TYPE_ENUM_ULONG_LONG, \
float: UTIL_TYPE_ENUM_FLOAT, \
double: UTIL_TYPE_ENUM_DOUBLE, \
long double: UTIL_TYPE_ENUM_LONG_DOUBLE, \
default: UTIL_TYPE_ENUM_OTHER)
typedef int32_t foo_t;
typedef float bar_t;
// IF YOU COMMENT OUT THE STATIC ASSERT, THE CODE WILL COMPILE AND WORKS AS EXPECTED
_Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");
int main(void)
{
foo_t foo;
bar_t bar;
printf("foo's type = %d\n", UTIL_TYPE_GET_TYPE_ENUM(foo));
printf("bar's type = %d\n", UTIL_TYPE_GET_TYPE_ENUM(bar));
if (UTIL_TYPE_GET_TYPE_ENUM(foo) != UTIL_TYPE_GET_TYPE_ENUM(bar))
{
printf("Not the same type!\n");
}
else
{
printf("Same type!\n");
}
return 0;
}
#endif
Compiler Error:
$ gcc foo.c
foo.c:35:49: error: expected expression before ‘,’ token
#define UTIL_TYPE_GET_TYPE_ENUM(x) _Generic((x), \
^
foo.c:77:17: note: in expansion of macro ‘UTIL_TYPE_GET_TYPE_ENUM’
_Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");
^~~~~~~~~~~~~~~~~~~~~~~
foo.c:35:49: error: expected expression before ‘,’ token
#define UTIL_TYPE_GET_TYPE_ENUM(x) (_Generic((x), \
^
foo.c:77:49: note: in expansion of macro ‘UTIL_TYPE_GET_TYPE_ENUM’
_Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");
^~~~~~~~~~~~~~~~~~~~~~~
foo.c:77:16: error: expression in static assertion is not an integer
_Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");
The argument to a _Generic
selection must be a valid C expression, the type of which is then examined. You provide a type name, which simply isn't an expression.
To get an expression of the types you are after, you can use a compound literal:
_Static_assert((UTIL_TYPE_GET_TYPE_ENUM((foo_t){0})==UTIL_TYPE_GET_TYPE_ENUM((bar_t){0})),"ERROR");
(foo_t){0}
and (bar_t){0}
are now expressions of the types you want to compare, and so may be used in the generic selection.
You can achieve what you want, or at least something close, with
#define SAME_TYPE(t1,t2) _Generic((t1){0}, t2: 1, default: 0)
_Static_assert(SAME_TYPE(foo_t, bar_t));
This does not involve enumerations that assume a finite set of types (not supporting struct types, etc.) and does not depend on the "GNU C" typeof
extension (which is not part of the C language).
This works for types, not expressions. It can easily be extended to the case where one argument is a type and the other is an expression. If you need to assert that two expressions have the same type, that's at least somewhat harder to do purely in C, and might not be possible. If you're in the special case where they're variables, the expression
1 ? &var1 : &var2
is a constraint violation if var1
and var2
have different types, but sadly GCC treats this as a warning by default rather than an error. I'm not aware of a way to make it into an error without a full -Werror
because it doesn't seem to be in its own warning group, just the anonymous on-by-default warnings...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With