提问人:TypeIA 提问时间:10/10/2023 最后编辑:TypeIA 更新时间:10/11/2023 访问量:89
是否可以推断成员函数指针模板参数的参数类型?
Can I deduce the argument types of a member function pointer template parameter?
问:
我经常使用 C 风格的库,其中回调和不透明指针模式很常见:
void callback(void *user_data, ...) { ... }
register_callback(callback, ptr_to_something_useful);
在使用 C++ 领域的此类库时,我经常发现自己这样做:
struct my_type {
static void static_callback(void *user_data) {
reinterpret_cast<my_type *>(user_data)->callback();
}
void callback() { }
};
my_type foo;
register_callback(my_type::static_callback, &foo);
可以是 lambda...但我认为我会很聪明,为了消除一些样板,我定义了以下 C++ 帮助程序:static_callback
template <typename T, typename... Args>
struct dispatcher {
template <void (T::*Func)(Args... args)>
static void call(void *arg, Args... args) {
T& obj = *reinterpret_cast<T *>(arg);
(obj.*Func)(args...);
}
};
现在我可以省略并写下:static_callback
register_callback(dispatcher<my_type>::call<&my_type::callback>, &foo);
不幸的是,当回调函数采用多个参数(在不透明的用户数据指针之后)时,这变得很糟糕,因为我的助手要求我显式指定类和参数类型:
struct my_type {
void callback(int a, char b, float c);
};
register_callback(dispatcher<my_type, int, char, float>::call<&my_type::callback>, &foo);
那么,我能做得更好吗?我尝试了几种方法让编译器以某种方式推断参数包中的参数类型,但没有任何效果。必须先在某处定义参数类型参数包,然后才能为成员函数指针指定非类型模板参数。我有一种感觉,可以使用模板参数和/或类型特征来提取参数类型,但我还没有找到可行的解决方案。auto
答案可以针对任何 C++ 标准版本。如果 Boost 中有这方面的东西,我也会接受(我不能在我当前的项目中使用 Boost,但我很想看看那里有什么实现的。
这里有一种“测试台”,你可以用它来玩:
struct my_type {
void callback(int, char, float) { }
};
void register_callback(void (*cb)(void *, int, char, float), void *user_data) {
cb(user_data, 42, 'X', 3.14f);
}
int main() {
my_type obj;
register_callback(/* your magic here */, &obj);
}
答:
1赞
super
10/11/2023
#1
您可以将指向成员函数的指针作为模板参数传递,然后使用第二个类型参数进行部分专用化。
#include <iostream>
struct my_type {
void callback(int i, char c, float f) {
std::cout << i << " " << c << " " << f << "\n";
}
};
void register_callback(void (*cb)(void *, int, char, float), void *user_data) {
cb(user_data, 42, 'X', 3.14f);
}
template <auto Func, typename T = decltype(Func)>
struct dispatcher;
template <auto Func, typename Ret, typename Class, typename... Args>
struct dispatcher<Func, Ret (Class::*)(Args...)> {
static void call(void* obj, Args... args) {
(reinterpret_cast<Class*>(obj)->*Func)(args...);
}
};
int main() {
my_type obj;
register_callback(&dispatcher<&my_type::callback>::call, &obj);
}
从技术上讲,这可以通过显式指定类型参数来滥用,但如果需要,您可以使用一些静态断言来防止这种情况。
评论
1赞
Ben Voigt
10/11/2023
请注意,表达式必须具有与 中相同的静态类型,否则将变成未定义的行为。不允许多态性。&obj
Class*
Ret (Class::*)(Args...)
reinterpret_cast
0赞
super
10/11/2023
@BenVoigt 好点子。我的直觉是,这里存在一些静态断言可以解决的危险。我最初的想法可能是多余的,因为通过显式指定参数进行误用会导致编译时错误。UB更讨厌。
0赞
TypeIA
10/11/2023
部分专业化的非常酷的技巧。它对我有用 - 谢谢。我会在合理的时间让其他人加入后接受这个答案。(在我的用例中,我不需要虚拟调用,但这是一个很好的限制。
上一个:如何防止函数模板参数推导
下一个:使用函数时避免使用指定模板参数
评论
magic_function<&my_type::callback> (®ister_callback, &obj);
template<typename T>struct magic{magic(T& obj) {} template<typename...Args> static void make_callback(void* p, Args...args) { ((T*)p)->callback(args...); }template<typename...Args> using StaticCallback = void(*)(void*, Args...); template<typename...Args> operator StaticCallback<Args...>() { return make_callback<Args...>; } }; register_callback(magic(obj), &obj);
callback