Quuxplusone / LLVMBugzillaTest

0 stars 0 forks source link

Explicit specialization after instantiation error #37082

Open Quuxplusone opened 6 years ago

Quuxplusone commented 6 years ago
Bugzilla Link PR38109
Status NEW
Importance P enhancement
Reported by Stephen Kelly (steveire@gmail.com)
Reported on 2018-07-09 11:12:13 -0700
Last modified on 2021-09-03 23:27:29 -0700
Version trunk
Hardware PC Windows NT
CC dblaikie@gmail.com, dgregor@apple.com, llvm-bugs@lists.llvm.org, llvm-bugzilla@jdrake.com, martin@martin.st, mati865@gmail.com, richard-llvm@metafoo.co.uk, yvesg@google.com
Fixed by commit(s)
Attachments
Blocks
Blocked by
See also
Clang does not accept code which CL.exe accepts. The example works if I remove
the dllexport.

C:\dev\src\playground\cpp>C:\dev\src\llvm\build\releaseprefix\msbuild-
bin\cl.exe explicit_instantiation.cpp
explicit_instantiation.cpp
explicit_instantiation.cpp(12,21):  error: explicit specialization of 's_foo'
after instantiation
template<> B* A<B>::s_foo = nullptr;
                    ^
explicit_instantiation.cpp(5,15):  note: implicit instantiation first required
here
    static T* s_foo;
              ^
1 error generated.

C:\dev\src\playground\cpp>cl.exe explicit_instantiation.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.14.26430 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

explicit_instantiation.cpp
Microsoft (R) Incremental Linker Version 14.14.26430.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:explicit_instantiation.exe
explicit_instantiation.obj
   Creating library explicit_instantiation.lib and object explicit_instantiation.exp

C:\dev\src\playground\cpp>type explicit_instantiation.cpp

template <typename T>
class A
{
    static T* s_foo;
};

class __declspec(dllexport) B : public A<B>
{
};

template<> B* A<B>::s_foo = nullptr;

int main()
{
    return 0;
}
Quuxplusone commented 4 years ago
Even simpler example:

 template <typename T>
 class __declspec(dllexport) A {
  public:
   static const T foo;
 };

 template <>
 const int A<int>::foo = 42;

Leads to:

 (...) error: explicit specialization of 'foo' after instantiation
 const int A<int>::foo = 42;
                   ^

 (...) note: implicit instantiation first required here
   static const T foo;
                  ^

I'm curious to know where the implicit instantiation comes from, and with which
template parameter.
Quuxplusone commented 3 years ago

Ran into this trying to build kirigami for x86_64-w64-windows-gnu using Clang 12.0.1. GCC also accepts this construct.

https://invent.kde.org/frameworks/kirigami/-/blob/master/src/libkirigami/platformtheme.h#L370

https://invent.kde.org/frameworks/kirigami/-/blob/master/src/libkirigami/platformtheme.cpp#L30

Quuxplusone commented 3 years ago
I ran into a Microsoft document that says

> The problem with a specialization of a class template is where to place
> the __declspec(dllexport); you are not allowed to mark the class template.
> Instead, explicitly instantiate the class template and mark this explicit
> instantiation with dllexport. For example:
>
> template class __declspec(dllexport) B<int>;

I tried that, but that gave a warning: 'dllexport' attribute ignored on
explicit instantiation definition [-Wignored-attributes].

But from the initial report, I guess the doc was mistaken (or out of date) and
you *are* allowed to mark the class template with dllexport.
Quuxplusone commented 3 years ago
(In reply to Jeremy Drake from comment #2)
> Ran into this trying to build kirigami for x86_64-w64-windows-gnu using
> Clang 12.0.1.  GCC also accepts this construct.

Do note that there are lots of subtle differences between GCC and MSVC about
where you should specify dllexport with respect to e.g. extern templates.

This is e.g. how it's done with respect to exporting full template
instantiations:

template <typename T> class A {
public:
  T add(T input) { return input + input; }
};

#ifdef _MSC_VER
// Header
extern template class A<int>;
// Implementation file
template class __declspec(dllexport) A<int>;
#else
// Header
extern template class __declspec(dllexport) A<int>;
// Implementation file
template class A<int>;
#endif

Also have a look at libcxx's config file which maps the dllexport/import
attributes to different locations depending on configuration
(dllexport/dllimport, and MSVC vs MinGW):

https://github.com/llvm/llvm-project/blob/llvmorg-13.0.0-rc1/libcxx/include/__config#L613-L655
(in particular, lines 629-635)

In general, libcxx doesn't set dllexport on a template declaration because this
causes the template to be instantiated (_LIBCPP_TEMPLATE_VIS is defined to an
empty string).

With the example shown earlier here, this form seems to work on both MSVC, GCC
and Clang in both modes:

template <typename T>
 class A {
  public:
   static const __declspec(dllexport) T foo;
};

template<> const int __declspec(dllexport) A<int>::foo = 42;

With GCC, this produces the same type of exports as the original example. With
MSVC, the original example (with dllexport on the template class itslef) it
also ends up dllexporting two instantiations of 'operator=()'.
Quuxplusone commented 3 years ago
(In reply to Martin Storsjö from comment #4)
> With the example shown earlier here, this form seems to work on both MSVC,
> GCC and Clang in both modes:
>
> template <typename T>
>  class A {
>   public:
>    static const __declspec(dllexport) T foo;
> };
>
> template<> const int __declspec(dllexport) A<int>::foo = 42;
>
>
> With GCC, this produces the same type of exports as the original example.
> With MSVC, the original example (with dllexport on the template class
> itslef) it also ends up dllexporting two instantiations of 'operator=()'.

Yes, that is also what we came up with https://github.com/msys2/MINGW-
packages/discussions/7589#discussioncomment-1198733

But, in the real-world example of Kirigami2 (rather than a minimal reproducer),
it results in different exports than what GCC made with the dllexport on the
template class: it is not exporting the destructor (the base class has a
virtual destructor, and the class itself has some non-trivial members), the
vtable, or the typeinfo for the instantiations for which the static member is
specialized.  I could not find a way to export those with Clang.
Quuxplusone commented 3 years ago
(In reply to Jeremy Drake from comment #5)

> But, in the real-world example of Kirigami2 (rather than a minimal
> reproducer), it results in different exports than what GCC made with the
> dllexport on the template class: it is not exporting the destructor (the
> base class has a virtual destructor, and the class itself has some
> non-trivial members), the vtable, or the typeinfo for the instantiations for
> which the static member is specialized.  I could not find a way to export
> those with Clang.

This doesn't seem to really affect kirigami2 however.  Making that change
doesn't seem to have changed the symbols exported by GCC, only that the symbols
exported by Clang differ from those exported by GCC.