Why doesn't unsigned typeof(var) work? const typeof(x) works fine, so why not?

Viewed 181

In a macro I would want to make the value the same width, however unsigned. So, a natural unsigned typeof(x) came to mind. However, it doesn't compile:

/home/guest/mc.c: In function ‘main’:
/home/guest/mc.c:14:36: error: expected ‘)’ before ‘typeof’
   14 |                         *((unsigned typeof(minus)*)&minus));

I wonder why it is so? Maybe I'm doing something wrong? Can I somehow add unsigned to the type of a variable?

PS. I've noted, that similar cast adding const works OK:

#define add_const(x) ((const typeof(x))(x))

Also, adding * to create a pointer type also works OK.

3 Answers

It looks like the only way is to enumerate all "interesting" types in a generic selection.

#define to_unsigned(x) _Generic((x),     \
                char: (unsigned char     )(x),     \
         signed char: (unsigned char     )(x),     \
                 int: (unsigned int      )(x),     \
               short: (unsigned short    )(x),     \
                long: (unsigned long     )(x),     \
           long long: (unsigned long long)(x),     \
             default: x)

short a;
typeof(to_unsigned(a)) ua;

This is not portable because implementations can provide more integral types. However typeof by itself is specific to gcc. Since gcc seems to provide all other standard integral types (size_t, uint8_t etc) as typedef names and not separate distinct types, these cases should cover everything.

Why doesn't unsigned typeof(var) work? const typeof(x) works fine, so why not?

Per clause 6.7 of the GCC 10.2 documentation:

A typeof construct can be used anywhere a typedef name can be used.

In the C grammar for declarations specified in C 2018 6.7, the declaration specifiers are partitioned into classes:

  • Storage-class specifiers, like static and extern.
  • Type specifiers, like int and float.
  • Type qualifiers, like const and volatile.
  • Function specifiers, like inline.
  • Alignment specifiers, like _Alignas(4).

Clause 6.7.2 shows that a typedef-name is a type specifier. Paragraph 2 specifies the ways they may be used. It gives a list of permissible combinations, including:

  • int, signed, or signed int
  • unsigned, or unsigned int
  • typedef name

Thus, unsigned can be used only in the particular combinations shown in this list, such as unsigned long long. A typedef name is shown as an item by itself, without unsigned. So it cannot be used with unsigned.

In contrast, const is a qualifier, which is a different kind of specifier, and the other clauses of 6.7 allow different kinds of specifiers to be mixed (with some additional restrictions that are not relevant here, such as having at most one storage-class specifier).

The fundamental reason why unsigned typeof(x) doesn't work but const typeof(x) does is that unsigned and const are two different syntax components. unsigned it a type specifier, while const is a type qualifier.

The list of type specifiers (note that a bare unsigned is syntactically equivalent to unsigned int:

6.7.2 Type specifiers

1. Syntax

      type-specifier:
             void
             char
             short
             int
             long
             float
             double
             signed
             unsigned
             _Bool
             _Complex
             atomic-type-specifier
             struct-or-union-specifier
             enum-specifier
             typedef-name

2. Constraints

...

  • unsigned, or unsigned int

So unsigned typeof(x) is the equivalent of unsigned int typeof(x).

const is a type qualifier:

6.7.3 Type qualifiers

1. Syntax

      type-qualifier:
             const
             restrict
             volatile
             _Atomic

const char is proper C code, assuming x is a char. unsigned int char is not proper C code.

Related