-1

I realize the title of the question is very confusing, but I cannot think of a better way to word this, so I'll explain it better with code.

I know you can select macros based on the number of parameters it receives using macro expansion and __VA_ARGS__ like in this dumb example:

#define EXP(x) x
#define SELECT_MACRO(_1, _2, macro) macro

#define FOO1(str)       printf(#str);
#define FOO2(str, num)  printf(#str, num);
#define SELECT_FOO(...) EXP(SELECT_MACRO(__VA_ARGS__, FOO2, FOO1)(__VA_ARGS__))

int main()
{
    int a = 5;  
    SELECT_FOO("Hello\n");
    SELECT_FOO("Number %d \n", 5);
    return 0;
}

I am interested in the usability of this method, since it means the user only needs to remember one macro, instead of two. I would like to do something similar, but for macros receiving functions, something that allows c and d to compile:

void PrintNumber(int n)
{
    printf("%d\n", n);
}

void PrintHello()
{
    printf("Hello\n");
}

#define BIND_FN_DATA(fn) [](int& num) { fn(num); }
#define BIND_FN(fn)  [](int& num) { (void)num; fn(); }

#define SELECT_BIND(...) // What should this look like?

int main()
{
    auto a = BIND_FN_DATA(PrintNumber);
    auto b = BIND_FN(PrintHello);

    // auto c = SELECT_BIND(PrintNumber);
    // auto d = SELECT_BIND(PrintHello);

    return 0;
}

This code is obviously simplified for the question, but essentially I'd like to check if the PrintXXX functions passed to the macro have 1 or 0 parameters. The different lambdas call the functions with or without the parameter, but their signatures need to be kept the same on both methods (in BIND_FN and BIND_FN_DATA). Can anyone think of a way to do this without adding any runtime cost?

Godbolt link: https://godbolt.org/z/cWqWPrvWj

avilapa
  • 11
  • 3

1 Answers1

3

Macros operate on a level of only tokens. They can't know anything about parameters of functions. Therefore it is rather pointless to use macros. In the end the relevant mechanism must be implemented in actual C++.

It is rather simple to write SELECT_BIND as a function template with C++20:

constexpr auto SELECT_BIND(auto fn) noexcept {
    return [=](int& num){
        if constexpr(requires { fn(num); }) {
            fn(num);
        } else {
            fn();
        }
    };
}

However, you need to choose which of the two you want to prefer if both fn() and fn(num) are valid. In the above I chose to prefer the latter.

With C++17, in auto fn, auto has to be replaced with F from a template<typename F> and required { fn(num); } must be replaced with std::is_invocable_v<F&, int&>.

Before C++17 this is a bit trickier, requiring e.g. partial specialization.

user17732522
  • 53,019
  • 2
  • 56
  • 105