8

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

JPAM69
  • 83
  • 4
  • 1
    Here are two minimal examples: [1](http://coliru.stacked-crooked.com/a/c1d2012c9d15bd97) and [2](http://coliru.stacked-crooked.com/a/7d0f33cb38da76ed). Both of them compile successfully in *gcc 4.8.2* and *clang 3.4* with `-Wall -Wextra -pedantic-errors` flags (in the both `c++03` and `c++11` standards). – Constructor Jun 26 '14 at 17:29
  • VS2008 is a really old compiler, given the pace at which C++ is currently evolving. Try upgrading to the latest and greatest. – TemplateRex Jun 27 '14 at 20:46
  • @Constructor Thanks for the checks! This means my code was in principle correct. – JPAM69 Jun 28 '14 at 15:25
  • @TemplateRex I have other versions of VS, but often are the customers who decide whatever reasons they have which compiler has to be used. – JPAM69 Jun 28 '14 at 15:27
  • This code runs and prints "5" in visual studio 2008 (version 9.0.30729.1 SP) – Kenny Ostrom Jul 08 '14 at 14:24
  • Try: `kernel>::apply(5);` – metal Jul 11 '14 at 13:49
  • @meta No luck (in the real application). Still the symbol is not there when linkin. – JPAM69 Jul 15 '14 at 12:07

1 Answers1

4

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

Niall
  • 30,036
  • 10
  • 99
  • 142
  • You don't need to cast to `void *`, I have a better solution (see my edit). – user541686 Jul 09 '14 at 00:00
  • @Niall Thanks a lot for the detailed answer. I'll try to integrate this idea into the concept. – JPAM69 Jul 09 '14 at 09:26
  • `foo` is a function template, not a simple function. Why is function-to-pointer conversion applicable here? – Constructor Jul 10 '14 at 20:21
  • @Constructor, true, it is not a simple function. No code for it exists until it is instantiated. Once instantiated, code for the function exists, just as a simple function. Given the context in which `foo` is used (a pointer is being expected) the compiler is able to deduced the template arguments, instantiate the function and then apply the conversion and continue compiling the code. The reference http://en.cppreference.com/w/cpp/language/function_template offers an exact sample of this `void (*ptr)(std::string) = f; // instantiates f(string)` under implicit instantiation. – Niall Jul 10 '14 at 21:18
  • @Constructor, something else worth mentioning. IIRC, the implicit function-to-pointer conversion is a old C compatibility thing. Many would argue that the more explicit form `&foo` should be favoured, but both forms are still applicable. – Niall Jul 10 '14 at 21:24
  • Could you provide a link to the exact place in the standard where the correctness of the code like `void (*ptr)(std::string) = f;` is shown? I have found some code pieces similar to [en.cppreference.com](http://en.cppreference.com)'s code samples at [temp.arg.explicit] 14.8.1/1 and [temp.deduct] 14.8.2/1 but there is no string like `void (*ptr)(std::string) = f` at them. I've only found something similar at C++14 drafts ([dcl.spec.auto] 7.1.6.4/12 and */15). – Constructor Jul 11 '14 at 11:41
  • @Constructor. With a search, I can't find a better example either; but I'll admit that I'm not too surprised. I think there is a lot of correct code that doesn't have an explicit example in the standard. I find the standard very dense. I did some more looking around though for more detail on the rationale here and found some more references that may be useful. I started by asking, what is `ptr`? `ptr` is basically declaring a pointer to a function (with the signature `void(string)`) and hence needs to be initialised with an address of a function with a matching signature (or `nullptr`)... next – Niall Jul 11 '14 at 13:11
  • From 13.4/1 `A function template name is considered to name a set of overloaded functions...` and `The overloaded function name can be preceded by the & operator.` - note the "may" not that it has to be preceded by &. What happens if the function is a template? 13.4/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.` – Niall Jul 11 '14 at 13:12
  • Of course I don't want to see the exact example in the standard but I want to see a place were such behavior of modern compilers is described. Your references are about function overloading but whether it has a relation to the present case? – Constructor Jul 11 '14 at 13:16
  • @Constructor. I wasn't looking for an exact match, apologies if it sounded like I did. I think that there may not be a suitable example at all, the writers may not have thought it was required. Section 13.4 seemed relevant here because it is referenced in one of the notes for 4.3. – Niall Jul 11 '14 at 13:35
  • Oh, I think you have found the last piece of the puzzle. Could you add all these reference to your answer (in the valid order, of course)? And I'll award the bounty to it... – Constructor Jul 11 '14 at 13:39
  • I was just thinking how I would add all of this to the answer. I'm sure I'll get a logical ordering to this. – Niall Jul 11 '14 at 13:42
  • @Constructor. Updated. On a side note, thanks for the discussion. I've enjoyed revisiting the rationale of this functionality and being pushed on the detail; it is always good to "sharpen the saw". – Niall Jul 11 '14 at 14:18