'Exporting class template with out-of-body definitions from DLL with MinGW-w64

I am facing trouble properly linking a trivial Windows executable to a trivial DLL with MinGW-w64 (based on GCC 11.3.0 from MSYS2) when class templates are involved. A minimal reproducer is as follows.

The complete code of the library (library.cpp) is

template <class T> class __attribute__((dllexport)) TestClass
{
    public:
        void member() { __builtin_printf("member from library\n"); }
        void other_member();
};

template class __attribute__((dllexport)) TestClass<int>;

template <class T> void __attribute__((dllexport)) TestClass<T>::other_member () {}

and I compile it using

g++ -std=c++11 library.cpp -o library.dll -shared -Wl,--out-implib,library.dll.a -Wl,--output-def,library.def

The complete code of the program (program.cpp) is

template <class T> class __attribute__((dllimport)) TestClass
{
    public:
        void member() { __builtin_printf("member from program\n"); }
        void other_member();
};

extern template class __attribute__((dllimport)) TestClass<int>;

int main (void)
{
    TestClass<int> test;
    test.member();
    return 0;
}

and I compile it using

g++ -std=c++11 program.cpp library.dll.a -o program.exe

The linking of the program to the DLL fails with undefined reference to TestClass<int>::member(). It turns out that the linking failure can be solved in two ways:

  1. The extern template statement in the program is commented out. Then the compiler uses the local version of the template and the program prints "member from program".
  2. The definition of TestClass<T>::other_member is commented out from the library. Then the program properly links to the TestClass<int>::member in the library and it prints "member from library".

I understand the first point, where the extern template is avoided and a local implicit instantiation takes place. This also happens when I compile the code with optimizations.

But the second point perplexes me. Why does the out-of-body definition of TestClass<T>::other_member break export of TestClass<T>::member?

Disclaimer: I am debugging someone else's program, so the design choices are not mine.



Solution 1:[1]

Template classes are usually used in header files. They are merely definitions, which only become actual code once instantiated with an actual type.

So in that respect I don't see how this can be exported as a symbol in a DLL:

template <class T> class __attribute__((dllexport)) TestClass

This does make sense, since the compiler can generate code because the type is known:

template class __attribute__((dllexport)) TestClass<int>;

Here is how it would make more sense in my opinion:

library.hpp

#if !defined(DLL_EXPORT_LIBRARY)
# if defined(_WIN32) && defined(BUILD_LIBRARY_DLL)
#  define DLL_EXPORT_LIBRARY __declspec(dllexport)
# elif !defined(STATIC) && !defined(BUILD_LIBRARY_STATIC)
#  define DLL_EXPORT_LIBRARY __declspec(dllimport)
# else
#  define DLL_EXPORT_LIBRARY
# endif
#endif

template <class T> class TestClass
{
    public:
        void member();
        void other_member();
};

template class DLL_EXPORT_LIBRARY TestClass<int>;

template <class T> void __attribute__((dllexport)) TestClass<T>::other_member () {}

library.cpp

#include <library.hpp>

template <class T> void TestClass<T>::member()
{
    __builtin_printf("member from library\n");
}

program.cpp

#include <library.hpp>

int main (void)
{
    TestClass<int> test;
    test.member();
    return 0;
}

build:

g++ -std=c++11 library.cpp -I. -DBUILD_LIBRARY_DLL -o library.dll -shared -Wl,--out-implib,library.dll.a -Wl,--output-def,library.def
g++ -std=c++11 program.cpp -I. library.dll.a -o program.exe

Solution 2:[2]

As far as I'm aware:
In library.cpp there is no (explicit) instantiation of the class template, or the member function of said class.

template class __attribute__((dllexport)) TestClass<int>;

Would be an (explicit) 'template specialization forward declaration'... (Because of course it would).
Long story short, if (and that's a bold assumption) I'm right you just need to add {} after TestClass<int> to have the type "defined".

To answer:

Why does the out-of-body definition of TestClass::other_member break export of TestClass::member?

I'm even less confident. But why are you defining a member function of a specialization of TestClass? ? Does it need a template at all?

cf: Explicit (full) template specialization

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Brecht Sanders
Solution 2