C-callback to function template:显式实例化模板

C-callback to function template: explicitly instantiate template

本文关键字:实例化 to function template C-callback      更新时间:2023-10-16

前提

我使用的是一个C库(来自C++(,它提供了以下接口:

void register_callback(void* f, void* data);
void invoke_callback();

问题

现在,我需要注册一个函数模板作为回调,这给我带来了问题。考虑以下代码:

template <typename T> void my_callback(void* data) { … }
int main() {
    int ft = 42;
    register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);
    invoke_callback();
}

这给了我以下链接器错误(在OS X上使用g++(GCC(4.5.1,但在编译器版本/平台的大多数其他组合上都有效(:

体系结构x86_64:的未定义符号

"void my_callback<int>(void*)", referenced from:  
  _main in ccYLXc5w.o

我觉得这是可以理解的。

第一个"解决方案">

这很容易通过显式实例化模板来解决:

template void my_callback<int>(void* data);

不幸的是,这在我的真实代码中不适用,因为回调是在函数模板内注册的,而且我不知道该函数将被调用到哪组模板参数,所以我不能为所有这些参数提供显式实例化(我正在编程一个库(。所以我的真实代码看起来有点像这样:

template <typename T>
void do_register_callback(T& value) {
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
    // Other things …
}
int main() {
    int ft = 42;
    do_register_callback(ft);
    invoke_callback();
}

第二个"解决方案">

函数模板是通过调用函数来隐式实例化的。因此,让我们这样做,但要确保调用没有实际执行(该函数有副作用(:

template <typename T>
void do_register_callback(T& value) {
    if (false) { my_callback<T>(0); }
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
}

即使启用了优化,这个似乎也能运行(这样编译器就可以删除死分支(。但我不确定这是否有一天会崩溃。我还发现这是一个非常丑陋的解决方案,需要一个长度解释性注释,以免将来的维护人员删除这些明显不必要的代码。

问题

如何实例化一个我不知道模板参数的模板这个问题显然是无稽之谈:我不能但是,有没有偷偷摸摸的办法?

除此之外,我的变通方法一定会成功吗

奖金问题

代码(特别是我将函数指针投射到void*的事实(也会产生以下警告:

ISO C++禁止在指向函数的指针和指向对象的指针之间进行强制转换

当使用CCD_ 2进行编译时。我能在不为库编写强类型C包装的情况下以某种方式消除警告吗(在我的情况下这是不可能的(?

在ideone上运行代码(添加强制转换使其可编译(

显然,真正的问题是我的原始代码中缺少static_cast

register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);

编译很好,但在使用GCC 4.5时会触发类似的错误。当使用GCC 4.2时,它甚至不进行编译,而是给出以下编译错误:

上下文信息不足,无法确定类型

一旦提供了这些"上下文信息",代码就会编译并链接:

register_callback(reinterpret_cast<void*>(
    static_cast<void(*)(void*)>(my_callback<int>)), &value);

我不知道是否真的需要强制转换,也不知道(如果是的话(为什么GCC 4.5允许我放弃它,然后无法实例化模板。但至少我在不使用黑客的情况下编译了代码。

这应该有效:

template <typename T>
void do_register_callback(T& value) {
   void (*callback)(void*) = my_callback<T>;
   register_callback(reinterpret_cast<void*>(callback), &value);
}

第一行强制编译器实例化该函数以生成地址,然后您可以愉快地传递该地址

编辑:让我在这个组合中加入另一个选项。使my_callback成为类模板的静态成员,如下所示:

template <typename T>
struct foo
{
static void my_callback(void* data) {
    T& x = *static_cast<T*>(data);
    std:: cout << "Call[T] with " << x << std::endl;
}
};

现在,在你的登记员中,你甚至不需要"演员阵容"。

template <typename T>
void do_register_callback(T& value) {
   register_callback(reinterpret_cast<void*>(&foo<int>::my_callback), &value);
}

实例化类模板的规则似乎与函数模板不同——即,要获取类成员的地址,类型被实例化

POSIX建议使用以下方法在函数指针类型和对象指针类型之间进行强制转换(在C99中未定义(:

typedef void function_type(void*);
function_type *p_to_function = &my_callback<T>;
void* p_to_data = *(void**)&p_to_function;
// Undefined:
// void* p_to_data = (void*)p_to_function;

请注意,在C++环境中,这将执行来自function_type**reinterpret_cast<void**>(&p_to_function)。与reinterpret_cast<void*>(p_to_function)不同,这不是未定义的,而是实现定义的。因此,编写依赖于实现的符合C++的代码可能是您的最佳选择。