The following minimal code compiles and links fine in 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);
}
but with Visual Studio 2008 it produces the error
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)".
Obviously the whole function implementation is there, but it seems that the compiler throws away the implementation of the foo
function. If the commented line is activated then the linker finds the symbol.
I think (as g++ compiles it fine) this is valid code, so I suppose there is some bug in VS 2008, or am I doing something wrong here? Does anyone knows a workaround/solution for this? The final code has to work with Visual Studio 2008 and in the real code it is not possible to guess all the template type combinations (i.e. I cannot explicitly instantiate the functions for all available types: here just T, in the real code, up to 5 template parameters with arbitrary classes are used).
In answer to the original question; is this a bug, are there workarounds?
Yes, it looks like you have found a bug in VS2008, I've tested it with VS2008 and VS2013.2 with the same linker error. I would encourage you to file a bug report with Microsoft. Are there workarounds, I believe there may be.
As you noted, it looks like the compiler "looses" the implicit instantiation of the template foo<int>
somewhere between the decay to void (*Function)(T,void*)
and when it is needed at link time. Having played with the code a little, I think it may involve the apply(M)
template and Microsoft's template parsing techniques; since, if apply
just takes an int
as its argument apply(int)
(i.e. no template) it seems happy to compile and link it.
To workaround this, the code can be changed as follows (adding the default constructor and changing the apply
call to be made from an instance of kernel
). I know this may look ugly; but it works around the issue and may help you work around the issue in your project.
#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);
}
The code compiles and links with VS2008, VS2013 and gcc.
With reference to the comments posted on the original question; why or how does this work with a modern compiler? It centres around two C++ facilities.
When supplying foo
as an argument for void(*Function)(T,void*)
, a decay takes place and a pointer is used, as if &foo
had been used.
Function-to-pointer conversion 4.3
1 An lvalue of function type T can be converted to a prvalue of type “pointer to T.” The result is a pointer to the function
Function-to-pointer conversions reference section 13.4 for additional rules when there are possible overloaded functions. Note the details on the usage of &
and the case where the function is a template (emphasis mine).
Address of overloaded function 13.4
1 A function template name is considered to name a set of overloaded functions... The overloaded function name can be preceded by the & operator.
2 If the name is a function template, template argument deduction is done (14.8.2.2), and if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization, which is added to the set of overloaded functions considered.
Given the pointer and the compiler's deduction of the type required T
for the function foo
being int
in this case. The compiler then generates the code for the function void foo(int,void*)
and then this is used during linking.
Implicit instantiation 14.7.1
3 Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist.
Quotes taken from C++ WD n3797
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With