3
votes

I'm trying to build a macro M which will expand to one of two possibilities, depeding on whether it has one, or more than one, arguments:

M(x)

should expand to

f(x)

While

M(x, "%d%d%d", 1, 2, 3)

should expand to

g(x, "%d%d%d", 1, 2, 3)

Where the function signatures are

f(int x);
g(int x, const char *fmt, ...);

There are various answers regarding the "overloading" of macros if the argument count is known; however their methods of determining the length of __VA_ARGS__ all work only to a finite, chosen number.

Is there any trick that might make a similar approach work for my "one argument / more than 1 arguments" case?

Note:

Overloading the functions is not an option because in my case they are actually constructors for two different classes.

1
To me, using two different constructurs behind one macro based on the number of arguments sounds pretty wrong. Unless one class is derived from the other, in which case some kind of overloaded/variadic argument factory or template with variadic argument factory function would be possible. - Mats Petersson
Especially if the two classes are not siblings of each other (from common base class), since then you'd have to know what the return type of the macro is anyway, at which point you could simply call the correct macro. An overloaded factory sounds like a much better option in either case. Macros should really only be used (in C++) where the pre-compile textual replacement part is important for functional correctness. - aruisdante
I agree this looks like a bad idea. Why do you want to use macros there ? - Félix Cantournet
Do not have time to write a full answer at the moment, but you check out my implementation here: github.com/SuperV1234/SSVUtils/blob/master/include/SSVUtils/… ArgCount.hpp is a macro that returns the count of its argument. Its implementation is in Generated.hpp - Vittorio Romeo
Are you really going to use more than 128 arguments? If so, I'm very glad I'm not going to have to work with your code! - Jonathan Leffler

1 Answers

1
votes

Simple. We just do a little probing to find out if a token is 1:

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,

#define IS_1(x) CHECK(PRIMITIVE_CAT(IS_1_, x))
#define IS_1_1 PROBE(~)

So IS_1 expands to 1 if the token is a 1, otherwise it expands to 0. So next, count the number of arguments(up to 8):

#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define NARGS(...) NARGS_SEQ(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

Then overload on whether it's equal to 1 or not:

#define M_1 f
#define M_0 g

#define M(...) CAT(M_, IS_1(NARGS(__VA_ARGS__)))(__VA_ARGS__)

So then you can call M like this:

M(x) // Expands to f(x)
M(x, "%d%d%d", 1, 2, 3) // Expands to g(x, "%d%d%d", 1, 2, 3)

Now, you can only count up to 64 arguments(my example counts up to 8), for standard C preprocessor(gcc can count up to 32767 arguments). If you need to have more arguments than it is better to use a sequence, which has no limit. So first write a method to convert the sequence back to arguments using sequence iteration:

#define TO_ARGS(seq) TO_ARGS_END(TO_ARGS_1 seq)
#define TO_ARGS_END(...) TO_ARGS_END_I(__VA_ARGS__)
#define TO_ARGS_END_I(...) __VA_ARGS__ ## _END
#define TO_ARGS_1(x) x TO_ARGS_2  
#define TO_ARGS_2(x) , x TO_ARGS_3  
#define TO_ARGS_3(x) , x TO_ARGS_2  
#define TO_ARGS_1_END
#define TO_ARGS_2_END
#define TO_ARGS_3_END

Next define the M macro to overload on whether there is one element in the sequence:

#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)

#define EAT(...)

#define M_1(seq) g(TO_ARGS(seq))
#define M_0(seq) f(TO_ARGS(seq))

#define M(seq) CAT(M_, IS_PAREN(EAT seq))(seq)

And then you can call it like this:

M((x)) // Expands to f(x)
M((x)("%d%d%d")(1)(2)(3)) // Expands to g(x, "%d%d%d", 1, 2, 3)

Of course in C++14 if you don't need source information then you can use variadiac templates instead:

template<class T>
auto M(T&& xs) -> decltype(f(std::forward<T>(x)))
{
    return f(std::forward<T>(x));
}

template<class T, class U, class... Ts>
auto M(T&& x, U&& y, Ts&&... xs) -> decltype(g(std::forward<T>(x), std::forward<U>(y), std::forward<Ts>(xs)...))
{
    return g(std::forward<T>(x), std::forward<U>(y),std::forward<Ts>(xs)...);
}

Or for constructors:

class M : f, g
{
    template<class T>
    M(T&& xs) : f(std::forward<T>(x))
    {}

    template<class T, class U, class... Ts>
    M(T&& x, U&& y, Ts&&... xs) : g(std::forward<T>(x), std::forward<U>(y), std::forward<Ts>(xs)...)
    {}
};