C++ 函数模板的C回调:显式实例化模板 前提

C++ 函数模板的C回调:显式实例化模板 前提,c++,macos,templates,undefined-reference,gcc4,C++,Macos,Templates,Undefined Reference,Gcc4,我正在使用一个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_callbac

我正在使用一个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();
}
template void my_回调(void*data){…}
int main(){
int-ft=42;
注册回拨(重新解释回拨(&my回拨),&ft);
调用_回调();
}
这给了我以下链接器错误(在OS X上使用g++(GCC)4.5.1,但在大多数其他编译器版本/平台组合上工作):

架构x86_64的未定义符号:

"void my_callback<int>(void*)", referenced from:  
  _main in ccYLXc5w.o
“void my_回调(void*)”,引用自:
_ccYLXc5w.o中的主管道
我觉得这是可以理解的

第一个“解决方案” 通过显式实例化模板,可以轻松解决此问题:

template void my_callback<int>(void* data);
模板void my_回调(void*数据);
不幸的是,这不适用于我的实际代码,因为回调是在函数模板中注册的,我不知道该函数将调用哪一组模板参数,所以我无法为所有这些参数提供显式实例化(我正在编写一个库)。所以我真正的代码看起来有点像这样:

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();
}
模板
无效do_寄存器_回调(T和值){
注册回调(重新解释强制转换(我的回调),&value);
//其他事情…
}
int main(){
int-ft=42;
不注册回调(ft);
调用_回调();
}
第二个“解决方案” 函数模板通过调用函数隐式实例化。让我们这样做,但确保调用没有实际执行(函数有副作用):

模板
无效do_寄存器_回调(T和值){
if(false){my_callback(0);}
注册回调(重新解释强制转换(我的回调),&value);
}
这似乎是可行的,即使启用了优化(以便编译器删除死分支)。但我不确定这是否有一天不会崩溃。我还发现这是一个非常丑陋的解决方案,需要一个长度解释性注释,以免将来的维护人员删除这个显然不必要的代码

问题: 如何实例化一个我不知道模板参数的模板?这个问题显然毫无意义:我不能但是有没有一个鬼鬼祟祟的方法

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

奖金问题 代码(特别是,我将函数指针转换为
void*
)也会产生以下警告:

ISO-C++禁止在函数指针和对象指针/p>之间进行强制转换 使用

-pedantic
编译时。我能在不为库编写强类型C包装器的情况下(在我的情况下这是不可能的),以某种方式消除警告吗

(添加强制转换以使其可编译)

这应该起作用:

template <typename T>
void do_register_callback(T& value) {
   void (*callback)(void*) = my_callback<T>;
   register_callback(reinterpret_cast<void*>(callback), &value);
}
现在,在你的登记员那里,你甚至不需要演员

模板
无效do_寄存器_回调(T和值){
register\u callback(重新解释\u cast(&foo::my\u callback),&value);
}

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

显然,真正的问题是在我的原始代码中缺少
静态\u cast

register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);
register\u callback(重新解释强制转换(&my\u callback),&ft);
这可以很好地编译,但在使用GCC4.5时会触发类似的错误。当使用GCC 4.2时,它甚至不编译,而是给出以下编译错误:

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

一旦提供了“上下文信息”,代码将编译并链接:

register\u callback(重新解释\u cast)(
静态_cast(我的_回调)),&value);

我不知道是否需要强制转换,以及(如果需要的话)为什么GCC4.5允许我禁用它,然后无法实例化模板。但至少我不用借助黑客就可以编译代码。

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

typedef无效函数类型(void*);
函数类型*p_to_函数=&my_回调;
void*p_to_data=*(void**)和p_to_函数;
//未定义:
//void*p_to_data=(void*)p_to_函数;

请注意,在C++-land中,这将从
函数类型**
执行
重新解释转换(&p\u to\u函数)
。这不是未定义的,而是实现定义的,与重新解释转换(p\u to\u函数)不同。因此,您最好编写依赖于实现的符合C++的代码。

为什么回调签名
void(*)(void*)
不在寄存器回调中?@David别问我,我一点也不知道。这来自第三方C库。为什么不使用
if(false){my_callback(value);}
而不是假设所有回调都使用指针值?它至少稍微不那么难看。@Chris:鉴于回调是用
void*
调用的,我想它总是使用指针参数。另外,使用
(void*)
而不是
reinterpret\u cast
可以消除警告。如果不行的话,你可以用工会。我想你是说我的回电这听起来很合理。不幸的是,它在我的编译器/平台组合上肯定不起作用。作为参考,您的代码也可以在我的平台上使用不同的编译器版本(4.2),也可以在不同的平台上使用我的编译器版本(4.5.1)。@zennehoy,@Nim:似乎很明显,我已经对其进行了修补。这是否意味着模板化函数有多个重载?如果只有一个模板,则不需要额外的“上下文信息”来确定类型(即,类型为
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);
}
register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);
register_callback(reinterpret_cast<void*>(
    static_cast<void(*)(void*)>(my_callback<int>)), &value);
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;