3

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.

9
  • 3
    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. Commented Mar 18, 2015 at 22:53
  • 1
    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. Commented Mar 18, 2015 at 23:04
  • I agree this looks like a bad idea. Why do you want to use macros there ? Commented Mar 18, 2015 at 23:40
  • 1
    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 Commented Mar 19, 2015 at 18:46
  • 1
    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! Commented Apr 4, 2015 at 1:13

2 Answers 2

1

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)...)
    {}
};
Sign up to request clarification or add additional context in comments.

9 Comments

Also, M(0,1,2,3,4,5,6,7,"foo") will result in error: pasting "IS_1_" and ""foo"" does not give a valid preprocessing token
That is because it has a limit of up to 8 arguments. You need to extend the NARGS_SEQ to handle more(the max is 64).
Unfortunately then your solution isn't much better than the various answers that were given in the comments, or linked in the question :|
I'm beginning to suspect that - despite including at least 3 turing-complete languages - C++14 is incapable of this.
Your question noted nothing about needing more than 64 arguments. If you need more than that then you need to use a sequence instead. I'll update my answer, in a bit.
|
1

I was having exactly the same question recently, to figure out how to distinguish between one argument vs more arguments, without having to count arguments. I think __VA_OPT__ can help.

The original problem can be solved as:

#define _M(x) f(x)
#define _M1(...) g(__VA_ARGS__)
#define M(x, ...) _M ## __VA_OPT__(1) (x __VA_OPT__(,) __VA_ARGS__)

Comments

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.