是否可以推断成员函数指针模板参数的参数类型?

Can I deduce the argument types of a member function pointer template parameter?

提问人:TypeIA 提问时间:10/10/2023 最后编辑:TypeIA 更新时间:10/11/2023 访问量:89

问:

我经常使用 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);
}
C++ 模板 指向成员的指针 template-argument-deduction

评论

1赞 Ben Voigt 10/11/2023
反转控制是否可以接受?magic_function<&my_type::callback> (&register_callback, &obj);
0赞 TypeIA 10/11/2023
@BenVoigt 经过进一步的思考 - 虽然我很想把它看作一个答案,而且我可以看到它如何使解决方案更容易,但我更喜欢一个公开静态函数(无反转)的模板。这是因为有时回调不是通过调用函数来注册的,而是通过在数据结构中存储静态函数指针和用户数据指针来注册的。反转解决方案不允许此用例。
0赞 Raymond Chen 10/11/2023
这个简单版本假定回调名为“callback”。您可以在此基础上进行构建,以允许命名回调。(成员函数指针上的模板,而不是对象上的模板。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);
0赞 TypeIA 10/11/2023
@RaymondChen 谢谢,但请将其作为答案发布,以便 (a) 清晰易读,(b) 可以正确投票和评论!
0赞 Raymond Chen 10/11/2023
如果您对它感到满意,您可以将其作为答案发布。这不是一个完整的解决方案,因为它硬编码了名称。callback

答:

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
请注意,表达式必须具有与 中相同的静态类型,否则将变成未定义的行为。不允许多态性。&objClass*Ret (Class::*)(Args...)reinterpret_cast
0赞 super 10/11/2023
@BenVoigt 好点子。我的直觉是,这里存在一些静态断言可以解决的危险。我最初的想法可能是多余的,因为通过显式指定参数进行误用会导致编译时错误。UB更讨厌。
0赞 TypeIA 10/11/2023
部分专业化的非常酷的技巧。它对我有用 - 谢谢。我会在合理的时间让其他人加入后接受这个答案。(在我的用例中,我不需要虚拟调用,但这是一个很好的限制。