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

时间:2014-06-26 15:10:50

标签: c++ templates visual-studio-2008 function-pointers lnk2019

以下最小代码在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个具有任意类的模板参数。)

1 个答案:

答案 0 :(得分:4)

原始问题

回答原来的问题;这是一个错误,有解决方法吗?

是的,看起来你在VS2008中发现了一个错误,我用VS2008和VS2013.2测试了它,并且链接器错误相同。我鼓励您使用Microsoft提交错误报告。是否有解决方法,我相信可能会有。

正如您所指出的,看起来编译器“失去”模板foo<int>的隐式实例化,介于衰减到void (*Function)(T,void*)之间以及链接时需要它时。在使用代码之后,我认为它可能涉及apply(M)模板和Microsoft的模板解析技术;因为,如果apply只需要int作为其参数apply(int)(即没有模板),那么它似乎很乐意编译并链接它。

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

#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 ++工具为中心。

  1. 函数指针衰减
    • 使用任何其他规则(例如模板)
  2. 隐式函数模板实例化
  3. 当提供foo作为void(*Function)(T,void*)的参数时,会发生衰减并使用指针,就像使用&foo一样。

      

    功能指针转换4.3

         

    1函数类型T的左值可以转换为“指向T的指针”的prvalue。结果是指向函数的指针

    当存在可能的重载函数时,函数到指针转换参考第13.4节以获取其他规则。请注意有关&用法的详细信息以及函数是模板的情况(强调我的)。

      

    重载功能的地址13.4

         

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

         

    2如果名称是函数模板,则模板参数推导完成(14.8.2.2),如果参数推导成功,则生成的模板参数列表用于生成单个函数模板专门化,它被添加到所考虑的重载函数集中。

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

      

    隐式实例化14.7.1

         

    3除非已明确实例化或明确专门化了函数模板特化,否则在需要存在函数定义的上下文中引用特化时,将隐式实例化函数模板特化。

    摘自C++ WD n3797