Avoiding duplicate symbol due to initialization of specialization in header?

风格不统一 提交于 2019-12-11 05:27:11

问题


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"

回答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.




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/47972746/avoiding-duplicate-symbol-due-to-initialization-of-specialization-in-header

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!