Virus721
Virus721

Reputation: 8335

Limit number of expanded arguments with __VA_ARGS__

I am overloading macros based on the number of arguments, as described in this question : Overloading Macro on Number of Arguments

When expanded, the __VA_ARGS__ "push" the macro names so that the right macro name is selected, based on the number of arguments, and then called.

My problem is that I want:

The problem with the solution in the linked question is that the 3rd argument is being used as the macro name, which doesn't work.

I would like to have a way to limit the number of arguments expanded when expanded __VA_ARGS__. Something like :

LIMIT_VA_ARGS_TO_2( ... ) ???

LIMIT_VA_ARGS_TO_2( 1 ) gives : 1

LIMIT_VA_ARGS_TO_2( 1, 2 ) gives : 1, 2

LIMIT_VA_ARGS_TO_2( 1, 2, 3 ) also gives : 1, 2

Is there a way to do that ?

Upvotes: 2

Views: 729

Answers (1)

Charles Ofria
Charles Ofria

Reputation: 2006

I don't see a simple way to accomplish this, but with a few helper macros, we can do it.

Let's build some of those helpers. First, if you know you have two or more arguments, then it's easy to grab the second one:

#define GET_ARG_2(A, B, ...) B

If you don't know that you have at least two arguments, you can always add on one more to be sure that there is a second on to grab. We can call the new one anything we like, but if we make it something that will never be a real argument, we can identify it later. Here I'm going to call it "EXPANDER(~)", and I'll explain why later. So the macro to ALWAYS grab the second argument, even if we only had one is:

#define FORCE_GET_ARG_2(...) GET_ARG_2( __VA_ARGS__, EXPANDER(~) )

Okay -- the next set of helper macros are all for indirection -- we put off when we perform the called operation to ensure that we first expand any macros that are part of the arguments BEFORE the listed operation is performed.

#define MERGE(A, B) A ## B
#define INDIRECT_GET_ARG_2(...) GET_ARG_2( __VA_ARGS__)
#define INDIRECT_MERGE(A, B) MERGE(A,B)

Now it's time to work a bit of magic. We're going to count how many arguments we have in a macro called COUNT_ARGS_CAP_2, where we return a 1 if there is one argument, or a 2 of there are two or more arguments. To do this, we're going to extend that magic EXPANDER(~) placeholder we used above.

We are going to use FORCE_GET_ARG_2 on all of the input. If there is a real second argument, it'll get it and it would count as one input argument. We'll then put the number 2 as our second argument (meaning that there were originally at least two arguments). If, however, we find EXPANDER(~) as the argument we pull, we want it to expand to take up 2 argument positions, with the second argument to be a 1 (pushing the 2 out of the way). Then we grab the second argument that we produce and it'll be the correct answer.

#define DO_SPECIAL_EXPANDER(x) x, 1

#define COUNT_ARGS_CAP_2(...) \
  INDIRECT_GET_ARG_2( INDIRECT_MERGE(DO_SPECIAL_, FORCE_GET_ARG_2( __VA_ARGS__ )), 2 )

If any argument other than EXPANDER(~) is prefixed with DO_SPECIAL_ it'll be meaningless and thrown away. BUT if EXPANDER(~) is prefixed by it, it'll turn into a real macro and expand to the two arguments that we need!

So, now we have a way of distinguishing between one argument and two or more. Let's use it to build the macro that you originally wanted!

#define LIMIT_VA_ARGS_TO_2( ... ) \
  INDIRECT_MERGE(LIMIT_VA_ARGS_V, COUNT_ARGS_CAP_2(__VA_ARGS__)) (__VA_ARGS__)
#define LIMIT_VA_ARGS_V1( A, ...) A
#define LIMIT_VA_ARGS_V2( A, B, ...) A, B

What we're doing here is calling a special sub-version of LIMIT_VA_ARGS (V1 or V2) based on the number of original arguments -- and we make sure it always produces the correct answer.

There may be a simpler way to accomplish all of this, but I couldn't easily find one. (I'd love to see if someone else did though!)

Upvotes: 4

Related Questions