英文:
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& 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论