0

Suppose we've got the following two functions:

void foo1(int p);
void foo2(int p, ...);

I'd like to write a macro to automatically expand to the proper one based on the number of arguments. I've used the following dirty/hacky way, but I'm curious whether there's a clean solution for this problem or not.

#define SELECT(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, NAME, ...) NAME
#define foo(...) SELECT(__VA_ARGS__,      \
    foo2, foo2, foo2, foo2,               \
    foo2, foo2, foo2, foo2,               \
    foo2, foo2, foo2, foo1)(__VA_ARGS__)

This way only works if foo2's number of arguments doesn't exceed 12. This is a drawback of my solution. I'm looking for a way without this limitation.

Update #1

The real problem: In Android NDK using the following functions we can write a log:

__android_log_print(int prio, const char *tag, const char *fmt, ...);
__android_log_write(int prio, const char *tag, const char *text);

To simplify the functions names, I define a macro called LOG:

#define LOG(...) __android_log_print(0, "test", __VA_ARGS__)

If I pass the macro a string literal, it's okay, but when I pass a variable, compiler generates the warning -Wformat-security. So, I'd like the macro calls with single argument to expand to __android_log_write and others to __android_log_print. My use cases for log: 1. string literal with/without arguments 2. single argument variable char *.

frogatto
  • 28,539
  • 11
  • 83
  • 129
  • Why not use c++ which does support polymorphism out of the box ? C was never designed for those cases. – dash-o Nov 24 '19 at 15:39
  • Another question: what the problem that you are trying to solve - how will the macro be used ? functions with 12 (or more) arguments are going to be difficult to use. Are all the arguments of the same type - and you are trying to pass 'list of int' ? – dash-o Nov 24 '19 at 15:40
  • @dash-o Yes, I know. I'm not using a function with >12 arguments. I'm just curious there's a better way to solve this problem or not using C not C++. My entire project is written in C, I'm not going to use C++ for this simple problem. – frogatto Nov 24 '19 at 16:45
  • What exactly are each of these two functions doing? There may be a way to combine them and avoid any macro trickery. – dbush Nov 24 '19 at 19:38
  • 1
    @dbush I've added the explanation for my real problem. – frogatto Nov 24 '19 at 20:08
  • Possible duplicate of [Overloading Macro on Number of Arguments](https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments) – KamilCuk Nov 24 '19 at 21:03

2 Answers2

1

If your compiler supports it, __VA_OPT__ from C++20 makes this more or less simple:

#define LOG(...) LOG1(__VA_ARGS__,)(__VA_ARGS__)
#define LOG1(x, ...) LOG2##__VA_OPT__(a)
#define LOG2(x) std::cout << "n = 1: " STR(x) "\n";
#define LOG2a(x, ...) std::cout << "n > 1: " STR(x, __VA_ARGS__) "\n";

#define STR(...) STR_(__VA_ARGS__)
#define STR_(...) #__VA_ARGS__

int main()
{
    LOG(1)       // Prints: `n = 1: 1`
    LOG(1, 2)    // Prints: `n > 1: 1, 2,`
    LOG(1, 2, 3) // Prints: `n > 1: 1, 2, 3,`
}
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
0

As per discussion in comments, C pre-processor is the not the ideal choice. It will not be able to add polymorphism.

As an alternative, consider leveraging the m4 macro engine, which has more power. It might produce some fun constructs. I would not usually recommend it for production (over C++ or Java). Good for Proof of concept projects, or prototyping.

More about GNU m4: https://www.gnu.org/software/m4/manual/

Consider x.m4,w hich will expand arbitrary call to foo with N arguments to foo(arguments).

define(`foo', `foo$#($*)')

void foo1(int v1) { }
void foo2(int v1, int v2) { }
void foo3(int v1, int v2, int v3) {}

void main(void)
{
   foo(a) ;
   foo(a, b) ;
   foo(a, b, c) ;
}

Expand with 'm4 x.m4`

void foo1(int v1) { }
void foo2(int v1, int v2) { }
void foo3(int v1, int v2, int v3) {}


void main(void)
{
   foo1(a) ;
   foo2(a,b) ;
   foo3(a,b,c) ;
}

When used in Makefile, you will usually append a .m4 suffix, you can build a rule similar to below to automatic build the intermediate '.c' file, which can be compiled using the default CC rule.

%.c: %.m4
   m4 <$^ -o $@
dash-o
  • 13,723
  • 1
  • 10
  • 37