以下最小代码在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个具有任意类的模板参数。)
答案 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 ++工具为中心。
当提供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除非已明确实例化或明确专门化了函数模板特化,否则在需要存在函数定义的上下文中引用特化时,将隐式实例化函数模板特化。