Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can c11's 'Generic' keyword be used within gcc _Static_assert

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");
like image 439
Brett Avatar asked Mar 03 '23 09:03

Brett


2 Answers

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.

like image 62
StoryTeller - Unslander Monica Avatar answered Mar 09 '23 05:03

StoryTeller - Unslander Monica


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

like image 35
R.. GitHub STOP HELPING ICE Avatar answered Mar 09 '23 07:03

R.. GitHub STOP HELPING ICE