【问题标题】:C-callback to function template: explicitly instantiate templateC-回调函数模板:显式实例化模板
【发布时间】: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&lt;T&gt;(value); } 而不是假设所有回调都将使用指针值?它至少不那么难看。
  • @Chris:鉴于回调是用void* 调用的,我猜它总是使用指针参数。
  • 另外,使用(void *) 而不是reinterpret_cast&lt;void *&gt; 可能会消除警告。如果不是,您可以随时使用联合。

标签: c++ macos templates undefined-reference gcc4


【解决方案1】:

显然,真正的问题是我的原始代码中缺少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 允许我不使用它,然后无法实例化模板。但至少我得到了无需借助 hack 即可编译的代码。

【讨论】:

  • 这是否意味着模板化函数有不止一个重载?如果只有一个模板,则不需要额外的“上下文信息”来确定类型(即类型将由模板的类型参数唯一定义)
  • @David 我没有。该代码与 ideone.com 代码中的代码完全相同。
  • @Konrad:既然你提到了修复,我意识到我已经不得不在类似的情况下使用static_cast。我没有细想它......但从来没有真正理解为什么它是必要的。
  • ..所以最后一行实际上适用于您的平台/编译器组合?而我发布的内容没有?有趣...
  • @Nim 让我重新评估一下。我认为你是对的,这实际上等同于static_cast。我现在没有时间,但我明天会做第一件事。
【解决方案2】:

这应该可行:

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);
}

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

【讨论】:

  • 我认为你的意思是 my_callback
  • 这听起来很合理。不幸的是,它绝对不适用于我的编译器/平台组合。作为参考,您的代码也可以在我的平台上使用不同的编译器版本 (4.2),它可以在不同的平台上与我的编译器版本 (4.5.1) 一起使用。
  • @zennehoy, @Nim:看起来很明显,我修补了它。
【解决方案3】:

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&lt;void**&gt;(&amp;p_to_function)。这不是未定义的,而是实现定义的,与reinterpret_cast&lt;void*&gt;(p_to_function) 不同。因此,最好的办法是编写依赖于实现的符合 C++ 的代码。

【讨论】:

  • 那么指针到指针到函数的转换要被取消引用并传递给回调注册?即*reinterpret_cast&lt;void**&gt;(&amp;p_to_function) - 这是实现定义的吗?
  • ..上面会产生关于类型双关指针取消引用的警告
  • @Nim 是的,reinterpret_cast 之间不相关的指针类型是实现定义的。
  • 必须在当天晚些时候,我不明白通过额外间接的额外步骤和简单地 reinterpret_cast 将函数指针指向 void* 之间的区别(它实际上是两种不同的指针类型——不是吗?)因此应该定义实现?
  • @Nim 当我使用“指针类型”时,它会从其含义中排除“指向函数类型的指针”(顺便将后者解析为(指向函数的指针)类型)。标准术语实际上是“对象指针类型”、“函数指针类型”和“成员指针类型”。我要修改我的答案。
猜你喜欢
  • 1970-01-01
  • 2010-09-18
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
  • 2019-04-15
  • 2011-06-23
  • 2014-02-26
  • 1970-01-01
相关资源
最近更新 更多