4

Suppose that I have a macro:

#define FOO(a, ...) if (a) foo(a, ## __VA_ARGS__)

This works well:

  • FOO(a) will be transformed to if (a) foo(a)
  • FOO(a, <some_parameters>) will be transformed to if (a) foo(a, <some_parameters>)

Is it possible to modify this macro, so only the first parameter of __VA_ARGS__ (if exists) passed to foo? So, I need:

  • FOO(a) to be transformed to if (a) foo(a)
  • FOO(a, b, <some_parameters>) to be transformed to if (a) foo(a, b)

I've tried to solve this with the same idea as BOOST_PP_VARIADIC_SIZE has, but it turned out this macro returns 1 for BOOST_PP_VARIADIC_SIZE() (empty arguments), which is not expected (I expected 0).

Note, that I need a solution, where b and <some_parameters> are evaluated only when bool(a) is true.

geza
  • 28,403
  • 6
  • 61
  • 135
  • 1
    Possible duplicate of [Overloading Macro on Number of Arguments](https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments) –  Dec 03 '18 at 16:31
  • @Frank: thanks for finding that question, based on that I could solve the problem. – geza Dec 03 '18 at 17:01
  • Do you really need MACRO ? simple overload seems to do the job. – Jarod42 Dec 03 '18 at 21:03
  • @Jarod42: yes, macro is needed, because I need to lazily evaluate FOO's parameters (I mean, in real code, I only call `foo`, when certain condition is true. If that condition is false, then `foo` is not called, and its parameters are not evaluated). – geza Dec 03 '18 at 21:31
  • @geza Are the type of `a`, `b` and `` all same ? – Hiroki Dec 04 '18 at 19:46
  • @Hiroki: no, they are different. – geza Dec 04 '18 at 19:54

2 Answers2

4

I propose a variadic macro with a generic lambda as a solution. The Important points are as follows:

  • It is difficult to pass both a and __VA_ARGS__ to a lambda as passed arguments in macro because when __VA_ARGS__ is empty

    [](){...}(a, __VA_ARGS__)
    

    becomes

    [](){...}(a,)
    

    and this , leads compilation error. Thus we split the first and second arguments of FOO into the captured and the passed ones respectively as follows. Then we can use a generic lambda in the macro even if __VA_ARGS__ is empty.

    [a](){...}(__VA_ARGS__)
    
  • The size of __VA_ARGS__ can be evaluated at compile-time as constexpr auto N. Then we can use if constexpr to separate function calls.

  • We can also apply if statement with initializer which is introduced from C++17 for if(a).

Then the proposed macro is as follows. This also works for you.

DEMO

#include <tuple>

#define FOO(a, ...)                                                           \
if(const bool a_ = (a); a_)                                                   \
[a_](auto&&... args)                                                          \
{                                                                             \
   const     auto t = std::make_tuple(std::forward<decltype(args)>(args)...); \
   constexpr auto N = std::tuple_size<decltype(t)>::value;                    \
                                                                              \
   if constexpr( N==0 ) {                                                     \
       return foo(a_);                                                        \
   }                                                                          \
   else {                                                                     \
       return foo(a_, std::get<0>(t));                                        \
   }                                                                          \
}(__VA_ARGS__)
Hiroki
  • 2,780
  • 3
  • 12
  • 26
  • Thanks! The problem with this approach is that `args` are always evaluated. I need a solution, where the evaluation of `args` can be controlled. Sorry, I didn't specified this in the question (but a comment describes this). Now I'll put this info into the question. But I'll give you an upvote anyways :) – geza Dec 05 '18 at 19:06
  • 1
    It’s very elegant work. Out of curiosity, why is `t` not `constexpr` as well? – jonspaceharper Dec 06 '18 at 01:47
  • 1
    @JonHarper thx. I think that `constexpr auto t` shows compile error if `__VA_ARGS__` exists because constexpr variables must be guaranteed to be initialized at compile-time and `args` are not initialized by a constant expression. I found similar discussion [here](https://stackoverflow.com/questions/23569309/c11-constexpr-function-pass-parameter). But the types of `args` and `N` are determined at compile-time because generic lambdas are essentially same with function templates and these are template parameters: https://stackoverflow.com/questions/17233547/how-does-generic-lambda-work-in-c14 . – Hiroki Dec 06 '18 at 16:50
  • @geza *if statement with initializer* which is introduced from C++17 seems to be useful for me. How about [this](https://wandbox.org/permlink/QoFC7bnRFAj41I52) ? Could you point out it if I still misunderstand the problem :) . If this provides a one of the solution, I will edit my answer whether you accept my answer or not. – Hiroki Dec 06 '18 at 17:24
  • @Hiroki: Nice work! I feel a little bit dumb, as the surrounding if is kinda trivial to add, I don't know why I didn't think of it :) Please add the `if` here as well, and I'll accept your answer! – geza Dec 06 '18 at 18:29
3

Based on this answer, I could solve the problem:

#define PRIVATE_CONCAT(a, b) a ## b

#define CONCAT(a, b) PRIVATE_CONCAT(a, b)

#define GET_100TH( \
    _01, _02, _03, _04, _05, _06, _07, _08, _09, _10,  \
    _11, _12, _13, _14, _15, _16, _17, _18, _19, _20,  \
    _21, _22, _23, _24, _25, _26, _27, _28, _29, _30,  \
    _31, _32, _33, _34, _35, _36, _37, _38, _39, _40,  \
    _41, _42, _43, _44, _45, _46, _47, _48, _49, _50,  \
    _51, _52, _53, _54, _55, _56, _57, _58, _59, _60,  \
    _61, _62, _63, _64, _65, _66, _67, _68, _69, _70,  \
    _71, _72, _73, _74, _75, _76, _77, _78, _79, _80,  \
    _81, _82, _83, _84, _85, _86, _87, _88, _89, _90,  \
    _91, _92, _93, _94, _95, _96, _97, _98, _99, PAR,  \
    ...) PAR

#define HAS_PARAMETER(...) GET_100TH(placeholder, ##__VA_ARGS__, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 0)

#define FIRST_PARAMETER_WITH_PREPENDED_COMMA0(...)
#define FIRST_PARAMETER_WITH_PREPENDED_COMMA1(a, ...) , a

#define FIRST_PARAMETER_WITH_PREPENDED_COMMA(...) CONCAT(FIRST_PARAMETER_WITH_PREPENDED_COMMA, HAS_PARAMETER(__VA_ARGS__))(__VA_ARGS__)

#define FOO(a, ...) if (a) foo(a FIRST_PARAMETER_WITH_PREPENDED_COMMA(__VA_ARGS__))
geza
  • 28,403
  • 6
  • 61
  • 135
  • This is ugly... Are you sure you want to poison your codebase with *this*? – YSC Dec 03 '18 at 17:02
  • 1
    @YSC: Yes, it's ugly. Boost's preprocessor library is ugly as well. But if you have a better solution, feel free to share with us :) – geza Dec 03 '18 at 17:03
  • 1
    @geza generate your code using python instead of the C++ preprocessor? :) – Yakk - Adam Nevraumont Dec 04 '18 at 04:02
  • @Yakk-AdamNevraumont: this solution is fine. Even if it's ugly, the interface is OK (I don't care about ugliness which is behind the interface. I care about interface, and usability. A comment can explain the ugliness inside). Using python for this task is unnecessary, and would make building more complex. But thanks for the idea anyway :) – geza Dec 04 '18 at 10:39