LNK2019 (VS 2008)使用模板函数指针全面实现模板函数

LNK2019 (VS 2008) with full implementation of template function using template function pointers

本文关键字:函数 指针 实现 VS 2008 LNK2019      更新时间:2023-10-16

下面的最小代码在GNU c++中编译和链接都很好:

#include <iostream>
// Simple function
template<class T>
void foo(T a,void* = 0) {
  std::cout << a << std::endl;
}
// A distpatching class
template<
         class T,
         void (*Function)(T,void*)
        >
class kernel {
public:
  // Function dispatcher
  template<class M>
  inline static void apply(M t) {
    Function(t,0);
  }
};
int main()
{
  kernel<int,foo>::apply(5);
  //foo(5,0);
}

但是在Visual Studio 2008中它会产生错误

error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""void __cdecl foo<int>(int,void *)" (??$foo@H@@YAXHPAX@Z)" in Funktion ""public: static void __cdecl kernel<int,&void __cdecl foo<int>(int,void *)>::apply<int>(int)" (??$apply@H@?$kernel@H$1??$foo@H@@YAXHPAX@Z@@SAXH@Z)".

显然,整个函数的实现是在那里,但似乎编译器丢弃了foo函数的实现。如果注释行被激活,则链接器会找到该符号。

我认为(作为g++编译它很好)这是有效的代码,所以我想在VS 2008中有一些错误,或者我在这里做错了什么?有人知道这个的变通/解决方案吗?最后的代码必须与Visual Studio 2008一起工作,在实际代码中,不可能猜测所有的模板类型组合(即,我不能显式地实例化所有可用类型的函数:这里只是T,在实际代码中,最多使用5个模板参数和任意类)。

原题

回答原来的问题;这是bug吗,有解决方法吗?

是的,看起来你在VS2008中发现了一个bug,我已经用VS2008和VS2013.2测试了它,有相同的链接器错误。我鼓励你向微软提交bug报告。有解决办法吗?我相信可能有。

正如你所注意到的,看起来编译器"丢失"了模板foo<int>的隐式实例化,在衰减到void (*Function)(T,void*)和链接时需要它之间的某个地方。玩了一会儿代码,我认为它可能涉及到apply(M)模板和微软的模板解析技术;因为,如果apply只是把int作为它的参数apply(int)(即没有模板),它似乎很乐意编译和链接它。

要解决这个问题,可以按如下方式更改代码(添加默认构造函数并更改从kernel的实例进行的apply调用)。我知道这可能看起来很丑;但是它可以解决这个问题,并且可以帮助您解决项目中的问题。

#include <iostream>
// Simple function
template<class T>
void foo(T a,void* = 0) {
  std::cout << a << std::endl;
}
// A distpatching class
template<class T,
         void(*Function)(T,void*)>
class kernel {
  void (*dummy)(T,void*);
public:
  kernel() : dummy(Function) {
    // "Force" an implicit instantiation...
    // dummy can be either a member variable or declared in
    // in the constructor here. It exists only to "force"
    // the implicit instantiation.
    // Alternative...
    //void* dummy = (void*)Function;
    //(void)dummy; // silence "unused" warnings
  }
  // Function dispatcher
  template<class M>
  inline static void apply(M t) {
    Function(t,0);
  }
};
int main()
{
  kernel<int,foo>().apply(5);
  // The kernel temporary instantiation is only needed once for the
  // following line to link as well.
  //kernel<int,foo>::apply(5);
}

代码与VS2008, VS2013和gcc进行编译和链接。


代码如何与现代编译器工作?

参考对原问题的评论;为什么或如何在现代编译器中工作?它以两个c++设施为中心。

    函数指针衰减
    • 附加规则(如模板)
    隐式函数模板实例化

当提供foo作为void(*Function)(T,void*)的参数时,发生衰减并使用指针,就像使用了&foo一样。

函数到指针的转换4.3

函数类型T的左值可以转换为"指向T的指针"类型的右值,结果是指向函数 的指针。

函数到指针的转换参考第13.4节,以了解可能存在重载函数时的附加规则。请注意&用法的细节,以及函数是模板的情况。

重载函数13.4的地址

1函数模板名称被认为是一组重载函数的名称…重载函数名前面可以加&操作符 .

2如果名称是函数模板,则进行模板实参演绎(14.8.2.2),如果实参演绎成功,则使用生成的模板实参列表生成单个函数模板特化,并将其添加到所考虑的重载函数集中。

给定指针和编译器对函数fooint所需的T类型的推断。然后编译器生成void foo(int,void*)函数的代码,然后在链接期间使用它。

隐式实例化14.7.1

除非函数模板特化已经被显式实例化或显式特化,否则当在需要存在函数定义的上下文中引用该特化时,该函数模板特化将被隐式实例化。

引自c++ WD n3797