Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling size_t in a generic selection

Tags:

c

size-t

c11

Is is possible to define a type-generic macro that supports all standard (unsigned) integer types, and also size_t?

foo((size_t)42) works with the example below as long as size_t refers to one of the standard unsigned integer types. But does the standard guarantee anywhere that this is the case, or could size_t also refer to an extended integer type?

void foo_bool(_Bool x);
void foo_uchar(unsigned char x);
void foo_ushort(unsigned short x);
void foo_uint(unsigned x);
void foo_ulong(unsigned long x);
void foo_ullong(unsigned long long x);

#define foo(x) _Generic((x), \
    _Bool: foo_bool, \
    unsigned char: foo_uchar, \
    unsigned short: foo_ushort, \
    unsigned: foo_uint, \
    unsigned long: foo_ulong, \
    unsigned long long: foo_ullong)(x)

like image 595
dpi Avatar asked Mar 30 '19 18:03

dpi


2 Answers

Is is possible to define a type-generic macro that supports all standard (unsigned) integer types, and also size_t?

Yes - with nesting.

size_t commonly matches a standard integer type like unsigned, unsigned long, unsigned long long, yet that is not required by C. The trick becomes how to use _Generic when size_t is the same as a standard type and when it is not a standard type. A similar issue applies to uintmax_t.

void foo_size_t(size_t x) { (void)x; puts("size_t"); }
void foo_bool(_Bool x) { (void)x; puts("bool"); }
void foo_uchar(unsigned char x){ (void)x; puts("unsigned char"); }
void foo_ushort(unsigned short x){  (void)x; puts("unsigned short"); }
void foo_uint(unsigned x) { (void)x; puts("unsigned"); }
void foo_ulong(unsigned long x){ (void)x; puts("unsigned long"); }
void foo_ullong(unsigned long long x){ (void)x; puts("unsigned long long"); }
void foo_none(){ puts("TBD"); }

#define foo(x) _Generic((x), \
  _Bool: foo_bool, \
  unsigned char: foo_uchar, \
  unsigned short: foo_ushort, \
  unsigned: foo_uint, \
  unsigned long: foo_ulong, \
  unsigned long long: foo_ullong, \
  default: foo_none)(x)

// Catch `size_t` as `size_t`    
// If `size_t` is distinctive, apply the size_t function
// If `size_t` is not distinctive, others will be caught here too
#define bar(x) _Generic((x), \
  size_t: foo_size_t(x), \
  default: foo(x) \
  )

int main(void) {
  foo((float)1);  // What happens when type matching nothing
  foo(1lu);
  foo((size_t)1);
  bar((float)1);  // What happens when type matching nothing
  bar((size_t)1);
  bar(1lu);
}

Output

TBD
unsigned long
unsigned long  // Overlap when size_t matches a type, else TBD
TBD
size_t // expect this as unsigned long if size_t was distinctive.
size_t  

Note that creative uses of _Generic may lead to issues as this expansion to C is still nascent.

like image 152
chux - Reinstate Monica Avatar answered Oct 01 '22 13:10

chux - Reinstate Monica


I don't know of any implementation that uses a non-standard type for this.

If it is really only size_t you are worried about:

  • You could have a foo_size function and put it in a default clause.
  • You could use a compile test to error out if size_t is not among the standard unsigned types.

Something like

static_assert(
         _Generic(sizeof(int),
                  unsigned char: 1,
                  unsigned short: 1,
                  ...
                  unsigned long long, 1,
                  default: 0),
          "size_t not among the standard unsigned types");
like image 31
Jens Gustedt Avatar answered Oct 01 '22 13:10

Jens Gustedt