Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LNK2019 (VS 2008) with full implementation of template function using template function pointers

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).

like image 986
JPAM69 Avatar asked Jun 26 '14 15:06

JPAM69


1 Answers

Original question

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.


How does the code work with modern compilers?

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.

  1. Function pointer decay
    • With any additional rules if applicable (e.g templates)
  2. Implicit function template instantiation

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

like image 141
Niall Avatar answered Oct 04 '22 15:10

Niall