Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does explicit template instantiation result in weak-template-vtables warning when there are out-of-line virtuals?

[Edited to show split between .cpp and hpp]

// file.hpp
class Base {
 public:
    virtual ~Base(void);
    Base(void);
    Base(const Base&) = default;
};

template<typename T>
class Derived: public Base {
 public:
    Derived(void);
    bool func(void);
};
// file.cpp
#include "file.hpp"

Base::~Base(void) {}
Base::Base(void) {}

template<typename T>
bool Derived<T>::func(void) {return true;}

template<typename T>
Derived<T>::Derived(void) {}

// required to avoid linker errors when using `Derived` elsewhere
template class Derived<int>;

The last line causes the following compiler warning in Clang v8.0 warning: explicit template instantiation 'Derived<int>' will emit a vtable in every translation unit [-Wweak-template-vtables]

My understanding is that because Base has at least one out-of-line virtual method, the vtables for all classes here would be anchored to this translation unit, hence this guidance in the LLVM coding standard. So why is this warning being generated?

See on Godbolt here with specific compiler version I'm using: https://godbolt.org/z/Kus4bq

Every similar question I find on SO is for classes with no out-of-line virtual methods so I have not been able to find an answer.

like image 274
lochsh Avatar asked May 08 '19 13:05

lochsh


People also ask

How the instantiation of function template happens?

When a function template is first called for each type, the compiler creates an instantiation. Each instantiation is a version of the templated function specialized for the type. This instantiation will be called every time the function is used for the type.

How do I force a template instantiation?

To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.

What is instantiation of a template?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation to handle a specific set of template arguments is called a specialization.


2 Answers

EDIT: I do not think this is a bug in Clang, but instead a consequence of a requirement of the Itanium C++ ABI: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vague-itemplate This section is referenced in the Clang source in RecordLayoutBuilder.cpp in computeKeyFunction:

Template instantiations don't have key functions per Itanium C++ ABI 5.2.6. Same behaviour as GCC.

The Itanium specification says that class template instantiations will be stored in a COMDAT section in the object file. COMDAT sections are used to store multiple definitions of the same object, which can then be unified at link-time. If the template was compiled the way I expected in my answer, with a key function anchoring it to a specific translation unit, then that wouldn't be compliant with this ABI.

I do think the warning is unhelpful, but as it's not part of -Wall or -Wextra I don't mind so much.

(Original post below)

I'm inclined to believe that this is due to a bug in Clang, reported here: https://bugs.llvm.org/show_bug.cgi?id=18733

Reposting content here in case the link breaks:

Rafael Ávila de Espíndola 2014-02-05 00:00:19 PST

Given

template<typename T>
class foo {
  virtual ~foo() {}
};

extern template class foo<int>;
template class foo<int>;

clang warns:

test.cpp:6:23: warning: explicit template instantiation 'foo<int>' will emit a vtable in every translation unit [-Wweak-template-vtables]
extern template class foo<int>;
                      ^
1 warning generated.

note that the warning points to the explicit template instantiation declaration, but is triggered by the definition. This should probably be checking if the definition is in a header. In a .cpp file there is only one translation unit that sees it.

Comment 1 David Faure 2016-02-13 12:21:27 PST

Yes, this false positive is indeed annoying. Thanks for reporting it. Clang developers: thanks for fixing it :-)

I'd be grateful for anyone else's opinion, but I agree with the bug reporter that this warning seems bogus in this case.

Although the last comment on the bug report refers to it as fixed, the bug is still listed with status "new", so I do not believe it is fixed.

like image 194
lochsh Avatar answered Oct 05 '22 06:10

lochsh


Does the line template class Derived<int>; exist in a header-file, which again is included in multiple source-files?

In that case the, vtable and methods of class Derived<int> will exist in multiple object-files. And the linker has to figure out what to do with those multiple copies.

How the compiler and linker is supposed resolve this according to the c++ standard, i am not sure of. But typically I don't care since the copies should normally look the same.

But to avoid this issue, you should put extern template class Derived<int>; in the header file, and template class Derived<int>; in exactly 1 compile unit (aka. source-file)

EDIT (to reflect your split of the code into "file.hpp" and "file.cpp"):

I have played a little bit with clang-6 (I that is the latest version I have)

To me the warning is of the type "If you do X, Y will happen". But it doesn't mean y has happened.

In this case Y is multiple vtables and that will only happen if you put template class Derived<int>; in multiple source files, which you dont't do.

The warning gets triggered for each template class Derived<int>; in your sources, so if you only see one warning, there will be only one vtable.

But there is a way to get rid of the warning: Do not have explicit instantiation, and rely on the compiler to instantiate the class implicitly.

For that you have to put all of your template definition in the header file. So move the definitions:

template<typename T>
bool Derived<T>::func(void) {return true;}

template<typename T>
Derived<T>::Derived(void) {}

into the header file, and remove extern template class Derived<int>; and template class Derived<int>;

like image 26
HAL9000 Avatar answered Oct 05 '22 08:10

HAL9000