LNK2019 (VS 2008)使用模板函数指针全面实现模板函数
LNK2019 (VS 2008) with full implementation of template function using template function pointers
下面的最小代码在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),如果实参演绎成功,则使用生成的模板实参列表生成单个函数模板特化,并将其添加到所考虑的重载函数集中。
给定指针和编译器对函数foo
为int
所需的T
类型的推断。然后编译器生成void foo(int,void*)
函数的代码,然后在链接期间使用它。
隐式实例化14.7.1
除非函数模板特化已经被显式实例化或显式特化,否则当在需要存在函数定义的上下文中引用该特化时,该函数模板特化将被隐式实例化。
引自c++ WD n3797
- QMetaObject invokeMethod的基于函数指针的语法
- C++-试图将函数指针推回到另一个CPP文件中的矢量时出错
- c++r值引用应用于函数指针
- 模板函数指针和lambda
- 是否可以将llvm::FunctionType转换为C/C++原始函数指针
- 带有类的函数指针
- () 函子后面的括号,而不是函数指针?
- 全局作用域中函数指针的赋值
- 使用"Task"函数指针队列定义作业管理器
- 将成员函数指针作为参数传递给模板方法
- 如何创建对象函数指针C++映射?
- 匹配函数指针作为模板参数?
- 通过函数指针定义类范围之外的方法
- 存储在类中的函数指针
- C++从函数指针数组调用函数
- 将返回值存储在函数指针数组的指针中是如何工作的?
- 整数键映射到头文件中的成员函数指针
- 从类成员函数到类 C 函数指针的转换
- 如何将内联匿名函数分配给C++函数指针
- 将字符缓冲区强制转换为函数指针