9

[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.

kula85
  • 73
  • 1
  • 9
lochsh
  • 366
  • 1
  • 12
  • Using g++ (gcc 6.3) there is no warning even with `-pedantic -Wall -Wextra`, and there are no linker error / compilation warning without `template class Derived;` – bruno May 08 '19 at 13:53
  • @bruno Thanks, good to know. The linker error is dependent on the linker, but my understanding is that many linkers will struggle to link the templated class without explicit instantiation. See here for more info http://www.cs.technion.ac.il/users/yechiel/c++-faq/separate-template-class-defn-from-decl.html As for the warnings, these are not part of the C++ standard and it does not surprise me that gcc does not have this warning. Unfortunately I cannot solve my problem by changing compilers and would like to understand why it is happening. – lochsh May 08 '19 at 13:56
  • 1
    If adding `template class Derived;` it must be placed into a unique source file, not in the header included by several sources – bruno May 08 '19 at 14:04
  • It is in a source file, not a header. I will update my question to make it clearer – lochsh May 08 '19 at 14:08
  • As I said above, I am not using g++. – lochsh May 08 '19 at 14:10

2 Answers2

5

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.

lochsh
  • 366
  • 1
  • 12
3

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>;

HAL9000
  • 2,138
  • 1
  • 9
  • 20
  • Using g++ `template class Derived;` must be present only one times, I mean in one of the sources (or none) but not in the header included by several sources. Declaring it _extern_ in the header included several times works (while it is defined in one source of course) – bruno May 08 '19 at 14:05
  • @HAL9000 The explicit instantiation is in the source file, not the header. See edit to question. Adding an extern declaration in the header doesn't remove the warning for me. Does it work for you with clang and `-Wweak-template-vtables`? – lochsh May 08 '19 at 14:54
  • Can you explain the reasoning for adding the extern declaration? I would have thought it would result in "Explicit specialisation after instantiation" compiler errors in general. – lochsh May 08 '19 at 15:10
  • if you have the `extern` keyword, you tell the compile that one of the source-files will explicitly instantiate the template, so there is no need for the compiler to implicitly instantiate it in every source file. (limited to those template parameters used with the `extern` declaration) – HAL9000 May 08 '19 at 15:30
  • Thanks for writing more info. I don't want to put the implementation in the header as it causes unnecessary bloat. I wonder if you are right about the warning -- my understanding of it was similar to that of -Wweak-vtables, where there's no way for the compiler to choose a TU to place the vtable due to all virtuals being inline, so it puts a copy in every TU that uses the class instead. More info here https://stackoverflow.com/a/23749273/6060352 These clang tests suggest my interpretation is right https://github.com/llvm-mirror/clang/blob/master/test/SemaCXX/warn-weak-vtables.cpp#L62 – lochsh May 08 '19 at 16:08
  • I wonder if this is an example of this clang bug https://bugs.llvm.org/show_bug.cgi?id=18733 it would certainly explain a lot – lochsh May 08 '19 at 17:37