2

I'm catching duplicate symbol errors due to definitions I am trying to provide in a header. Here's the error from the Minimal, Complete, and Verifiable example. The header files and source files are shown below.

$ clang++ main.cpp x.cpp y.cpp -o main.exe 2>&1 | c++filt
duplicate symbol Id<S>::id in:
    /tmp/main-3f2415.o
    /tmp/long long-d62c28.o
duplicate symbol Id<T>::id in:
    /tmp/main-3f2415.o
    /tmp/long long-d62c28.o
duplicate symbol Id<S>::id in:
    /tmp/main-3f2415.o
    /tmp/unsigned long long-bfa6de.o
duplicate symbol Id<T>::id in:
    /tmp/main-3f2415.o
    /tmp/unsigned long long-bfa6de.o
ld: 4 duplicate symbols for architecture x86_64

Here is a similar question, but it does not involve a specialization: Static member initialization in a class template. This question has the specialization but it is for MSVC and not Clang: How to initialize a static member of a parametrized-template class. And this question states to put it in the source (*.cpp) file but we are aiming for the header file to avoid Clang 3.8 and 'Id<S>::id' required here, but no definition is available warnings: Where should the definition of an explicit specialization of a class template be placed in C++?

GCC, ICC, MSVC, SunCC and XLC are OK with the initialization. Clang and LLVM is giving me the trouble. Clang and LLVM also has trouble with explicit instantiations of specializations and extern, so its it own special kind of hell.

We support C++03 though C++17, so we have to be careful of the solution. Naively I tried placing the initialization of the specializations in an unnamed namespace to keep the symbols from escaping the translation units, but it resulted in compile errors.

How do we avoid duplicate symbol definitions when initializing and specializing a template class in a header?


Below is the MCVE, which is a cat main.cpp a.h s.h s.cpp t.h t.cpp x.cpp y.cpp. The problem seems to be with a.h, which provides the specialization and initialization; and the source files x.cpp and y.cpp, which include a.h.

main.cpp

#include "a.h"
#include "s.h"
#include "t.h"

int main(int argc, char* argv[])
{
    uint8_t s = Id<S>::id;
    uint8_t t = Id<T>::id;
    return 0;
}

a.h

#ifndef A_INCLUDED
#define A_INCLUDED

#include <stdint.h>

template <class T> class Id
{
public:
    static const uint8_t id;
};

class S;
class T;

template<> const uint8_t Id<S>::id = 0x01;
template<> const uint8_t Id<T>::id = 0x02;

#endif

s.h

#ifndef S_INCLUDED
#define S_INCLUDED

class S {
public:
    S();
};

#endif

s.cpp

#include "s.h"

S::S() {}

t.h

#ifndef T_INCLUDED
#define T_INCLUDED

class T {
public:
    T();
};

#endif

t.cpp

#include "t.h"

T::T() {}

x.cpp

#include "a.h"

y.cpp

#include "a.h"
jww
  • 97,681
  • 90
  • 411
  • 885

3 Answers3

6

Clang/LLVM is not the problem. You're simply running into undefined behavior with a touch of no diagnostic required. The fix is simple. You need to put your specialization(s) in one translation unit. i.e,

a.cpp:

#include "a.h"

template<> const uint8_t Id<S>::id = 0x01;
template<> const uint8_t Id<T>::id = 0x02;

Then the command line:

clang++ main.cpp a.cpp x.cpp y.cpp -o main.exe 2>&1 | c++filt

And voila. It works.

  • Thanks. There is no `a.cpp`. How do we keep it in a header? Or is it simply not possible do do it under Clang and LLVM? (I tried using a *.cpp file, but then Clang complained about a missing definition at the time of instantiation). – jww Dec 26 '17 at 00:07
  • You do not keep it in a header which you include *multiple* times. You have multiple definition errors *because* you include `a.h` multiple times in different translation units. What you do instead is forward declare what you must, and instantiate what you can in a dedicated `*.cpp`. It *might* work to compile & link your entire project in one go, but is not guaranteed -- the definition of a 'translation unit' is somewhat fuzzy (i.e. it is *not necessarily* equivalent to a single `*.cpp` file). – user268396 Dec 26 '17 at 00:11
  • 1
    @user268396 Sorry, but that's wrong. Translation unit is explicitly defined in [\[lex.separate\]](http://eel.is/c++draft/lex#separate-1): "The text of the program is kept in units called source files in this document. A source file together with all the headers and source files included via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion preprocessing directives, is called a translation unit." The part about instantiating in a single `*.cpp` file is correct though. – user9139968 Dec 26 '17 at 00:19
  • @user9139968 - That's one of the problems I seem to be misunderstanding. `id` is a `static` and `const` variable so I hoped it would have internal linkage and not escape the translation unit. But I also understand it is not file scope. Hence the reason I wanted to use an anonymous namespace, but that resulted in a compile error. – jww Dec 26 '17 at 00:31
  • 1
    @jww You'll still need to use `template<> const uint8_t Id::id;` in the header to declare an explicit instantiation of `id`, while having the definition in a source file (.cpp, or a .h that is only included once). Maybe making the ids `constexpr`where supported may also be a solution. – 1201ProgramAlarm Dec 26 '17 at 01:06
  • @jww static on member variables has nothing to do with linkage – Passer By Dec 26 '17 at 02:14
4

You are violating the ODR (one definition rule) for Id::id and Id::id. They get defined for each translation unit they are included in and thus show up when you link.

Depending on your intent with the id's for class S and T, you have to give them a unique home. One might be to park them in main.cpp. main.cpp add

template<> const uint8_t Id<S>::id = 0x01;
template<> const uint8_t Id<T>::id = 0x02;

Or, put the id of S in s.cpp and id of T in t.cpp:

s.cpp

#include "s.h"
#include "a.h"

template<> const uint8_t Id<S>::id = 0x01;

and the equivalent for t.cpp.

Don't forget to remove any traces of S and T in a.h.

But, if they are part of the a.h interface, the create an a.cpp and define them there.

Bo R
  • 2,334
  • 1
  • 9
  • 17
1

You can use unnamed namespace which can make your class have internal linkage, e.g.

#ifndef A_INCLUDED
#define A_INCLUDED

#include <stdint.h>

namespace {
    template <class T> class Id
    {
    public:
        static const uint8_t id;
    };
}

class S;
class T;

template<> const uint8_t Id<S>::id = 0x01;
template<> const uint8_t Id<T>::id = 0x02;

#endif

Replace this code with the contents in a.h in your example, then it will work because Id<T> in one translation unit is different from that in another translation unit due to internal linkage, thus one-definition rule is not violated.

xskxzr
  • 12,442
  • 12
  • 37
  • 77