Overloading functions with the C preprocessor
2014-09-15 ⋅void myfunc(int);
void myfunc(int, int);
C doesn't have a way to do this. You'll often see code like this:
void myfunc_1(int);
void myfunc_2(int, int);
However, this can be implemented, albeit in a slightly convoluted way, using a very controversial tool: the C preprocessor.
Here's the code in myfunc.h:
#ifndef MYFUNC_H
#define MYFUNC_H
void _myfunc_1(int a)
{
puts("Got 1 arg");
}
void _myfunc_2(int a, int b)
{
puts("Got 2 args");
}
#define _MYFUNC_SEL(_1, _2, _3, ...) _3
#define myfunc(...) _MYFUNC_SEL(__VA_ARGS__, _myfunc_2, _myfunc_1)(__VA_ARGS__)
#endif // MYFUNC_H
and the main file:
#include "myfunc.h"
int main()
{
myfunc(1); // Got 1 arg
myfunc(1, 2); // Got 2 args
return 0;
}
What's going on here? The easiest way to see is to look at what happens in the preprocessor.
The first call ( myfunc(1)
) expands to this:
_MYFUNC_SEL(1, _myfunc_2, _myfunc_1)(1)
Remember, __VA_ARGS__
expands to the ...
arguments. The _MYFUNC_SEL
macro returns the third argument, _myfunc_1
. Therefore, the expansion is ends up being:
_myfunc_1(1)
The second call ( myfunc(1, 2)
) expands to this:
_MYFUNC_SEL(1, 2, _myfunc_2, _myfunc_1)(1, 2)
Again, _MYFUNC_SEL
returns the third argument, which, in this case, is _myfunc_2
.
Now you're probably beginning to realize how simple the underlying logic is.
However, what if you need to overload by types instead? In C11, that's easily possible using the _Generic feature. See this for more info. Here's the new myfunc.h:
#ifndef MYFUNC_H
#define MYFUNC_H
void _myfunc_1_int(int a)
{
puts("Got 1 int");
}
void _myfunc_1_void(void* x)
{
puts("Got 1 pointer");
}
void _myfunc_2(int a, int b)
{
puts("Got 2 args");
}
#define _MYFUNC_1(x) _Generic((x), int: _myfunc_1_int, void*: _myfunc_1_void)(x)
#define _MYFUNC_SEL(_1, _2, _3, ...) _3
#define myfunc(...) _MYFUNC_SEL(__VA_ARGS__, _myfunc_2, _MYFUNC_1)(__VA_ARGS__)
#endif // MYFUNC_H
and the new main source file:
#include "myfunc.h"
int main()
{
myfunc(1); // Got 1 int
myfunc((void*)NULL); // Got 1 pointer
myfunc(1, 2); // Got 2 args
return 0;
}
Let's review the expansion process:
myfunc(1)
_MYFUNC_SEL(1, _myfunc_2, _MYFUNC_1)(1)
_MYFUNC_1(1)
_Generic((1), int: _myfunc_1_int, void*: _myfunc_1_void)(1)
Now, the _Generic
function basically is like pattern-matching on types. (See the linked article above for more info.) The _Generic
call evaluates to:
_myfunc_1_int(1)
See how the magic works?
All in all, you can see how much you can do with the preprocessor. Just don't abuse it, because the error messages kind of...well...suck. I'll put a way to get better errors in a future post.