【发布时间】:2011-10-07 17:58:37
【问题描述】:
前提
我正在使用一个提供以下接口的 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++ 禁止在函数指针和对象指针之间进行转换
使用-pedantic 编译时。我能否以某种方式摆脱警告,无需为库编写强类型 C 包装器(在我的情况下这是不可能的)?
Running code on ideone(添加强制转换以使其编译)
【问题讨论】:
-
注册回调中为什么没有回调签名
void (*)(void*)? -
@David 别问我,我一点头绪都没有。这是来自第三方 C 库。
-
为什么不使用
if(false) { my_callback<T>(value); }而不是假设所有回调都将使用指针值?它至少不那么难看。 -
@Chris:鉴于回调是用
void*调用的,我猜它总是使用指针参数。 -
另外,使用
(void *)而不是reinterpret_cast<void *>可能会消除警告。如果不是,您可以随时使用联合。
标签: c++ macos templates undefined-reference gcc4