6
votes

Given this code:

//header.h
template <class T>
class Foo
{
public:
  Foo(T t) : t(t) {}
  T t;
};

//source1.cpp:
#include "header.h"
extern template class Foo<int>;
int main()
{
  Foo<int> f(42);
}

By my understanding, this program should not link, since there should be no definition of class Foo<int> anywhere (extern template should prevent this). With VC++ 11 (Visual Studio 2012), this does however compile and link. In GCC, it doesn't:

source1.cpp:(.text+0x15): undefined reference to `Foo<int>::Foo(int)'

If I link with source2.cpp however, it works (as I expect i should) :

#include "header.h"
template class Foo<int>;

According to this blog post, extern template should have been supported since VC10. http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx

On a side note, is there a way to list the names in an object file on Windows / Visual Studio? On Linux I would do:

$ nm source1.o
U _ZN3FooIiEC1Ei      <- "U" means that this symbol is undefined.
0000000000000000 T main
1

1 Answers

16
votes

C++11 14.7.2/10 "Explicit instantiation" says:

Except for inline functions and class template specializations, explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer.

And the constructor in you class template Foo<T> is inline. VS2012 will work the way that you expect if you structure the header like so:

//header.h
template <class T>
class Foo
{
public:
  Foo(T t);
  T t;
};

template <class T>
Foo<T>::Foo(T t) : t(t) 
{
}

so that the constructor isn't inline.

The paragraph from the standard I quoted above does include the following note:

[ Note: The intent is that an inline function that is the subject of an explicit instantiation declaration will still be implicitly instantiated when odr-used (3.2) so that the body can be considered for inlining, but that no out-of-line copy of the inline function would be generated in the translation unit. — end note ]

Looking at the assembly code created when the ctor is inlined, an out-of-line copy of the ctor is placed in the object file (even though the ctor is never even called if you compile the example with optimizations on), so MSVC doesn't appear to be following the intent of the standard. However, notes are not normative, so I believe that MSVC's behavior is conforming.


Regarding your side question about dumping symbols from object files built with MSVC, you can use the dumpbin utility:

When compiling the example with the non-inline constructor:

dumpbin /symbols test.obj

...

008 00000000 UNDEF  notype ()    External     | ??0?$Foo@H@@QAE@H@Z (public: __thiscall Foo<int>::Foo<int>(int))
             ^^^^^
...

Compiling the example with the ctor inlined:

00A 00000000 SECT4  notype ()    External     | ??0?$Foo@H@@QAE@H@Z (public: __thiscall Foo<int>::Foo<int>(int))
             ^^^^^