16
FUNC(param);

When param is char *,dispatch to func_string.

when it's int,dispatch to func_int

I think there may be a solution to this,as variable types are known at compile time..

1
  • Even if this were possible, it would be fugly. C wasn't designed for it. Use structs and unions, you can even get run-time dispatch that way. Commented Aug 31, 2011 at 11:25

7 Answers 7

19

This will be possible with C1X but not in the current standard.

It will look like this:

#define cbrt(X) _Generic((X), long double: cbrtl, \
                          default: cbrt, \
                          float: cbrtf)(X)
Sign up to request clarification or add additional context in comments.

1 Comment

As this question pops up first on google: this is now possible using C11 en.cppreference.com/w/c/language/generic
11

Variable types are known to the compiler, but not to the preprocessor (which sees the code simply as unstructured text a stream of tokens, and performs only simple replacement operations on it). So I am afraid you can't achieve this with C macros.

In C++, they invented templates to solve such problems (and more).

1 Comment

This is a false conclusion. The preprocessor does not need to know argument types, if you can arrange a macro expansion so that the compiler can perform do the analysis. See arnaud576875's answer how to solve this for the gnu compiler. Also, the preprocessor does not see the code as unstructured text, but as a token stream, which is a important difference.
6

You can test for the characteristics of the types.

For example, int can hold a negative value, while char* can't. So if ((typeof(param))-1) < 0, param is unsigned:

if (((typeof(param))-1) < 0) {
    do_something_with_int();
} else {
    do_something_with_char_p();
}

The compiler obviously optimizes this out.

Try it here: http://ideone.com/et0v1

This would be even easier if the types had different sizes. For example, if you want to write a generic macro than can handle different character sizes:

if (sizeof(param) == sizeof(char)) {
    /* ... */
} else if (sizeof(param) == sizeof(char16_t)) {
    /* ... */
} else if (sizeof(param) == sizeof(char32_t)) {
    /* ... */
} else {
   assert("incompatible type" && 0);
}

GCC has a __builtin_types_compatible_p() builtin function that can check for types compatibility:

if (__builtin_types_compatible_p(typeof(param), int)) {
    func_int(param);
} else if (__builtin_types_compatible_p(typeof(param), char*)) {
    func_string(param);
}

Try it here: http://ideone.com/lEmYE

You can put this in a macro to achieve what you are trying to do:

#define FUNC(param) ({                                                \
    if (__builtin_types_compatible_p(typeof(param), int)) {           \
        func_int(param);                                              \
    } else if (__builtin_types_compatible_p(typeof(param), char*)) {  \
        func_string(param);                                           \
    }                                                                 \
})

(The ({...}) is a GCC's statement expression, it allows a group of statements to be a rvalue.

The __builtin_choose_expr() builtin can choose the expression to compile. With __builtin_types_compatible_p this allows to trigger an error at compile-time if the type of param is not compatible with both int and char*: (by compiling somehting invalid in this case)

#define FUNC(param)                                                        \ 
    __builtin_choose_expr(__builtin_types_compatible_p(typeof(param), int) \ 
        , func_int(param)                                                  \ 
        , __builtin_choose_expr(__builtin_types_compatible_p(typeof(param), char*) \ 
            , func_string(param)                                           \ 
            , /* The void expression results in a compile-time error       \ 
                 when assigning the result to something.  */               \ 
            ((void)0)                                                      \ 
        )                                                                  \ 
    )

This is actually a slightly modified example from __builtin_choose_expr docs.

4 Comments

func_int(x);, func_string(x); and put the whole thing inside a do { } while(0)
I originally didn't enclosed the code in do{}while() because that was not a macro ;-)
-1 typeof is not C but also a gcc extension. What makes you think that an address can't have the 'sign' bit set?
((char*) -1) < 0 is always false if the pointer type is unsigned. (assuming pointers are unsigned, aren't they ?)
4

There is no possibility to run time check types in C89 / ANSI C, but there is an extension to gcc which allows it. typeof or something along those lines if I remember. I saw it in the Linux Kernel once.

In kernel.h:

#define min(x, y) ({                \
typeof(x) _min1 = (x);          \
typeof(y) _min2 = (y);          \
(void) (&_min1 == &_min2);      \
_min1 < _min2 ? _min1 : _min2; })

Take a look at this article: GCC hacks in the Linux kernel

When I first saw this I actually asked a question here on SO about:

min macro in kernel.h

I'm not quite sure exactly how you would use it to solve your problem, but it's something worth taking a look at.

1 Comment

this is not a runtime type check - as C is not dynamically typed, type information is only available at compile-time
1

You can't do this with a macro. Macro's value are substituted at compile time and are not intepreted. They are just substitutions.

Comments

1

Variable types are indeed known at compile time, however macro expansion takes place before compilation. I suggest you implement 2 overloaded functions instead of a macro.

Comments

1

my definition of a generic: a structured abstract type which can only be fully defined with an input of other concrete types

this sounds exactly like a macro to me

pardon the psudo c code, my c is rusty

#include <stdio.h>

// todo: ret=self needs vec3##generic_t##_copy(self, ret);
// not to mention we should probably be using __builtin_add_overflow
// __builtin_add_overflow might actually itself be a reasonably generics method example
// please bear with me
#define GENERIC_VEC3_ADD(generic_t) \
    generic_t vec3##generic_t##_add(generic_t self, generic_t other) {\
        generic_t ret = self;\
        ret[0] += other [0];;\
        ret[1] += other [1];\
        ret[2] += other [2];\
        return ret;\
    }

#define GENERIC_VEC3_FREPR(generic_t, printf_ts) \
    int vec3##generic_t##_frepr(generic_t self, FILE fd)\
        rerurn fprintf(fd, "<vec3##generic_t (##printf_ts##, printf_ts##, printf_ts##)>", \
        self[0], self[1], self[2]);\
    }

// here is the generic typedef, with some methods
#define GENERIC_VEC3(genetic_t, printf_ts) \
    typedef vec3##generic_t generic_t[3];\
    GENERIC_VEC3_ADD(generic_t) \
    GENERIC_VEC3_FREPR(generic_t, printf_ts)

// later we decide what types we want this genic for
GENERIC_VEC3(int, %ul)

// and use our generic
int main()
{
    vec3int foo = { 1, 2, 3 };;
    vec3int bar = { 1, 2, 3 };;
    vec3int sum = vec3int_add(foo, bar);
    vec3int_frepr(sum, stderr);
    fprintf(stderr, "\n");
    exit EXIT_SUCCESS;
}

1 Comment

The only problem with this is when you want to have a GENERIC_VEC3(int, %ul) and a GENERIC_VEC3(unsigned long long, %ull) in the same file. Because generic_t is already typedef'd to one type, you can't reuse it in the same translation unit.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.