Is there a way to select between two macros depending on the number of parameters on a function sent as the macro parameter?

huangapple go评论68阅读模式
英文:

Is there a way to select between two macros depending on the number of parameters on a function sent as the macro parameter?

问题

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

#define SELECT_BIND(...) EXP(SELECT_MACRO(__VA_ARGS__, BIND_FN_DATA, BIND_FN)(__VA_ARGS__))
英文:

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

答案1

得分: 3

宏在仅处理标记级别的情况下运作。它们无法知道函数参数的任何信息。因此,使用宏是没有意义的。最终,相关机制必须在实际的C++中实现。

在C++20中,将SELECT_BIND编写为函数模板相当简单:

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

但是,如果fn()fn(num)都是有效的,您需要选择其中一个。在上面的示例中,我选择了较后者。

在C++17中,在auto fn中,auto必须替换为F,来自template<typename F>,而required { fn(num); }必须替换为std::is_invocable_v<F&, int&>

在C++17之前,这有点棘手,需要例如部分特化。

英文:

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&amp; 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&lt;typename F&gt; and required { fn(num); } must be replaced with std::is_invocable_v&lt;F&amp;, int&amp;&gt;.

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

huangapple
  • 本文由 发表于 2023年2月8日 23:33:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75388107.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定