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

JPA*_*M69 8 c++ templates function-pointers lnk2019 visual-studio-2008

以下最小代码在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);
}
Run Code Online (Sandbox Code Playgroud)

但是使用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)".
Run Code Online (Sandbox Code Playgroud)

显然整个函数实现都存在,但似乎编译器抛弃了foo函数的实现.如果激活注释行,则链接器将找到该符号.

我认为(因为g ++编译得很好)这是有效的代码,所以我想VS 2008中有一些错误,或者我在这里做错了什么?有谁知道解决方法/解决方案吗?最终的代码必须与Visual Studio 2008一起使用,并且在实际代码中不可能猜出所有模板类型组合(即我无法显式实例化所有可用类型的函数:这​​里只是T,在实际代码中,直到使用具有任意类的5个模板参数).

Nia*_*all 4

原问题

\n\n

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

\n\n

是的,看起来您在 VS2008 中发现了一个错误,我已经用 VS2008 和 VS2013.2 对其进行了测试,具有相同的链接器错误。我鼓励您向Microsoft提交错误报告提交错误报告。有没有解决办法,我相信可能有。

\n\n

正如您所指出的,编译器看起来像是在衰减到链接时需要模板foo<int>之间的某个位置“丢失”了模板的隐式实例化。void (*Function)(T,void*)稍微玩了一下代码,我认为这可能涉及到apply(M)模板和微软的模板解析技术;因为,如果apply仅将 anint作为其参数apply(int)(即没有模板),它似乎很乐意编译和链接它。

\n\n

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

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

该代码使用VS2008、VS2013和gcc进行编译和链接。

\n\n
\n\n

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

\n\n

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

\n\n
    \n
  1. 函数指针衰减\n\n
      \n
    • 任何附加规则(如果适用)(例如模板)
    • \n
  2. \n
  3. 隐式函数模板实例化
  4. \n
\n\n

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

\n\n
\n

函数到指针的转换 4.3

\n\n

1 函数类型 T 的左值可以转换为指向 T 的指针 \xe2\x80\x9c 类型的纯右值。\xe2\x80\x9d 结果是指向函数的指针

\n
\n\n

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

\n\n
\n

13.4 重载函数地址

\n\n

1 函数模板名称被认为是命名一组重载函数...重载函数名称前面可以加上 & 运算符

\n\n

2 如果名称是函数模板,则完成模板参数推导(14.8.2.2),如果参数推导成功,则使用生成的模板参数列表生成单个函数模板特化,将其添加到重载集合中考虑的功能。

\n
\n\n

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

\n\n
\n

隐式实例化 14.7.1

\n\n

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

\n
\n\n

引自C++ WD n3797

\n