frogatto
frogatto

Reputation: 29285

Expanding to different values based on length of __VA_ARGS__

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 *.

Upvotes: 0

Views: 229

Answers (2)

HolyBlackCat
HolyBlackCat

Reputation: 96831

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,`
}

Upvotes: 1

dash-o
dash-o

Reputation: 14493

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 $@

Upvotes: 0

Related Questions