https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89087

            Bug ID: 89087
           Summary: Dllexport for explicit template instantiation with
                    nested classes loses nested class
           Product: gcc
           Version: 8.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: martin at martin dot st
  Target Milestone: ---

Created attachment 45537
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=45537&action=edit
Sample code showing the issue

When an explicit template instantiation of a template class with a nested class
is declared with the dllexport attribute, only members from the outer class
actually gets the embedded export directive.

A caller that sees the explicit template instantiation declaration won't emit
those symbols but produce undefined references to them (both for the outer and
inner class), relying on the template instantiation in a different translation
unit.

If relying on the dllexport attribute for exporting the relevant symbols, only
the outer class' members are exported, and linking to the dll fais.


To showcase the problem:

header.h:
template <class T> struct outer {
  void f();
  struct inner {
    void f();
  };
};

template <class T> void outer<T>::f() {}
template <class T> void outer<T>::inner::f() {}

extern template class
#ifdef DLLEXPORT
__declspec(dllexport)
#elif defined(DLLIMPORT)
__declspec(dllimport)
#endif
outer<char>;


lib.cpp:
#define DLLEXPORT
#include "header.h"

template class outer<char>;

caller.cpp:
#define DLLIMPORT
#include "header.h"

int main(int argc, char* argv[]) {
  outer<char> a;
  a.f();
  outer<char>::inner b;
  b.f();
  return 0;
}


Building this fails in this way:
$ make
x86_64-w64-mingw32-g++    -c -o caller.o caller.cpp
x86_64-w64-mingw32-g++    -c -o lib.o lib.cpp
x86_64-w64-mingw32-g++ -shared -o lib.dll lib.o -Wl,--out-implib,liblib.dll.a
x86_64-w64-mingw32-g++ -o caller.exe caller.o -L. -llib
caller.o:caller.cpp:(.text+0x28): undefined reference to
`outer<char>::inner::f()'
collect2: error: ld returned 1 exit status
Makefile:5: recipe for target 'caller.exe' failed
make: *** [caller.exe] Error 1


The template instantiation in lib.cpp does get both outer and inner function
definitions:
$ x86_64-w64-mingw32-nm lib.o
<snip>
0000000000000000 T _ZN5outerIcE1fEv
0000000000000000 T _ZN5outerIcE5inner1fEv

And the caller gets undefined references to the same:
$ x86_64-w64-mingw32-nm caller.o 
<snip>
0000000000000000 T main
                 U _ZN5outerIcE1fEv
                 U _ZN5outerIcE5inner1fEv

But only the outer function actually ended up exported from the DLL:
$ x86_64-w64-mingw32-objdump -s lib.o
<snip>
Contents of section .drectve:
 0000 202d6578 706f7274 3a225f5a 4e356f75   -export:"_ZN5ou
 0010 74657249 63453166 45762200           terIcE1fEv".    


If the DLL is linked with -Wl,--export-all-symbols, both functions are exported
from the DLL and linking succeeds.


This is contrary to MSVC (which admittedly has got an entirely different C++
ABI). In MSVC, the caller emits the inner class' methods despite the explicit
template instantiation (both when the template instantiation was marked
dllimport, but also if dllimport is omitted):

With dllimport:
$ cl -nologo -c caller.cpp 
caller.cpp
$ x86_64-w64-mingw32-nm caller.obj 
<snip>
0000000000000000 T ?f@inner@?$outer@D@@QEAAXXZ
                 U __imp_?f@?$outer@D@@QEAAXXZ
0000000000000000 T main

Without dllimport:
$ cat caller.cpp | sed 's/^#def.*//' > caller-nodllimport.cpp
$ ~/msvc2017/bin64/cl -nologo -c caller-nodllimport.cpp 
caller-nodllimport.cpp
$ x86_64-w64-mingw32-nm caller-nodllimport.obj 
<snip>
0000000000000000 T ?f@inner@?$outer@D@@QEAAXXZ
                 U ?f@?$outer@D@@QEAAXXZ
0000000000000000 T main



To solve this (short of requiring using -Wl,--export-all-symbols on any library
that uses explicit template instantiation with nested classes), the dllexport
either needs to cover the nested class, or an explicit template instantiation
should only be considered to cover the outer class.

Reply via email to