cc23

Using _Generic to implement IS_POINTER(p) with rvalue in C23?


I want to implement IS_POINTER(P) using _Generic. Using this answer that implements IS_ARRAY(A) as a starting point, I have:

#define IS_POINTER(P)   \
  _Generic( &(P),       \
    typeof(*P) ** : 1,  \
    default       : 0   \
  )

Given:

int a[1];
int *p;

int main() {
  printf( "IS_POINTER(a) = %d\n", IS_POINTER(a) );
  printf( "IS_POINTER(p) = %d\n", IS_POINTER(p) );
}

I get:

$ gcc -std=c2x is_pointer.c && ./a.out
IS_POINTER(a) = 0
IS_POINTER(p) = 1

which is correct. However, if I instead do:

int i;

int main() {
  printf( "IS_POINTER(&i) = %d\n", IS_POINTER(&i) );
}

i.e., take the address of an object using & to yield a pointer, I get:

is_pointer.c:5:36: error: cannot take the address of an rvalue of type 'int *'
  printf( "IS_POINTER(&i) = %d\n", IS_POINTER(&i) );
                                   ^          ~~
is_pointer.c:5:13: note: expanded from macro 'IS_POINTER'
  _Generic( &(P),           \
            ^ ~
1 error generated.

I understand that you can't take the address of an rvalue, i.e., &(&i). My question is: is there a way to rewrite IS_POINTER() to make it also work in cases where you explicitly take the address via & of an object and pass that to IS_POINTER()?

I've been kicking it around and haven't come up with a way.


Solution

  • You can modify your macro to make the passed argument a compound literal (which is an lvalue), to cover the &i scenario.

    #define IS_POINTER(P)            \
      _Generic( &(typeof(P)){0},     \
        typeof(*P)**  : 1,           \
        default       : 0            \
      )
    

    However, this macro will still not work for plain variables which aren't pointers, because then typeof(*P) is a syntax error. I'm not aware of a way to distinguish between plain variables and pointers in standard C. There are some non-standard extensions you can use: Check if a macro argument is a pointer or not