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.

Community
  • 1
  • 1
mic_e
  • 5,594
  • 4
  • 34
  • 49
  • 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. – Mats Petersson Mar 18 '15 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. – aruisdante Mar 18 '15 at 23:04
  • I agree this looks like a bad idea. Why do you want to use macros there ? – Félix Cantournet Mar 18 '15 at 23:40
  • This is part of a logging system that allows two ways of creating a message: One to be filled with `<<` and one to be filled by a traditional format string. The `<<` one requires an internal `std::stringstream`. – mic_e Mar 18 '15 at 23:44
  • 1
    Do not have time to write a full answer at the moment, but you check out my implementation here: https://github.com/SuperV1234/SSVUtils/blob/master/include/SSVUtils/Core/Preprocessor/ `ArgCount.hpp` is a macro that returns the count of its argument. Its implementation is in `Generated.hpp` – Vittorio Romeo Mar 19 '15 at 18:46
  • @VittorioRomeo: Unfortunately your implementation seems to work only up to 128 arguments. In practice, it should be enough... – mic_e Mar 19 '15 at 19:17
  • @mic_e: You can generate code for more than 128 arguments, I created this script: https://github.com/SuperV1234/SSVUtils/blob/master/extra/ssvpp_generator.cpp – Vittorio Romeo Mar 19 '15 at 19:36
  • 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! – Jonathan Leffler Apr 04 '15 at 01:13
  • @JonathanLeffler: No, with very high certainity I will never have a function with more than 6 arguments - with the possible exception of constructors. However, I would be _very_ glad if I didn't have to work with code that randomly fails with a near-undebuggable preprocessor error once you use it in a legal, but uncommon way. – mic_e Apr 04 '15 at 11:24

1 Answers1

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)...)
    {}
};
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • 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` – mic_e Apr 03 '15 at 12:18
  • 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). – Paul Fultz II Apr 03 '15 at 16:17
  • Unfortunately then your solution isn't much better than the various answers that were given in the comments, or linked in the question :| – mic_e Apr 04 '15 at 11:26
  • I'm beginning to suspect that - despite including at least 3 turing-complete languages - C++14 is incapable of this. – mic_e Apr 04 '15 at 11:31
  • 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. – Paul Fultz II Apr 04 '15 at 18:47
  • My question noted that all other answers only work for a finite number of args. – mic_e Apr 04 '15 at 19:34
  • @mic_e: There really is no known way of supporting an infinite number of arguments. Just create a generation script that generates `NARGS` for a number of arguments you desire. Make it generate preprocessor code for 512 arguments and you're set for life. [Example.](https://github.com/SuperV1234/SSVUtils/blob/master/extra/ssvpp_generator.cpp#L109-L133) – Vittorio Romeo Apr 04 '15 at 19:49
  • @VittorioRomeo C limits the number of arguments to 127, so using over 64 may not be portable. Of course, I have never reached that limit in my code. – Paul Fultz II Apr 04 '15 at 19:58
  • @Paul: Didn't know about the C 127 argument limit. Does the C++ standard have a similar limitation? – Vittorio Romeo Apr 04 '15 at 20:03