Imagine we have three .h
files:
f.h
:
template <typename T> class Class {public: Class() {} T id(T x) { return x; }};
g.h
:
template <typename T> class Class {public: Class() {} T id(T x) { return x + 100; }};
h.h
:
template <typename T> class Class {public: Class(); T id(T x); };
Now, we also have three .cpp
files:
f.cpp
:
#include "f.h"
int f(int x) { Class<int> t; return t.id(x); }
g.cpp
:
#include "g.h"
int g(int x) { Class<int> t; return t.id(x); }
h.cpp
:
#include "h.h"
int h(int x) { Class<int> t; return t.id(x); }
Compiling them gives us f.o
, g.o
and h.o
. Now let's throw in this main.cpp
:
#include <stdio>
extern int f(int);
extern int g(int);
extern int h(int);
int main() {
std::cout << f(1) << std::endl;
std::cout << g(2) << std::endl;
std::cout << h(3) << std::endl;
}
A-a-and let's do g++ main.cpp f.o g.o h.o
. Now comes my actual surprise: Since those three .o
files contain three different definitions for int Class<int>::id(int)
, I expect to get a linking error. However, what I get is a working a.out
, which prints 1 2 3
. And if I reorder .o
files in the command, it will print 101 102 103
.
And now for the actual questions: How exactly does the linker perform linking in this case? How does it figures out what instantiation of Class<int>
to keep and what to throw away? And why it does not complain about multiple definitions?
nm
utility gives following output for nm f.o g.o h.o
:
f.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 t .text$_ZN5ClassIiE2idEi
00000000 t .text$_ZN5ClassIiEC1Ev
00000000 T __Z1fi
00000000 T __ZN5ClassIiE2idEi
00000000 T __ZN5ClassIiEC1Ev
g.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 t .text$_ZN5ClassIiE2idEi
00000000 t .text$_ZN5ClassIiEC1Ev
00000000 T __Z1gi
00000000 T __ZN5ClassIiE2idEi
00000000 T __ZN5ClassIiEC1Ev
h.o:
00000000 b .bss
00000000 d .data
00000000 d .eh_frame
00000000 t .text
00000000 T __Z1hi
U __ZN5ClassIiE2idEi
U __ZN5ClassIiEC1Ev
Clearly, f.o
and g.o
both export symbol __ZN5ClassIiE2idEi
, and h.o
imports this symbol (capital letters mean external linkage). And it leads to no errors. Why?
This problem is actually well known: You're violating the ODR. Due to the nature of templates the compiler and linker don't notice that. What happens is the following:
The linker assumes that the ODR has NOT been violated and is therefor free to use ANY of the instantiations of the template (aka. all instantiations of the template with the same parameters lead to the exact same code being generated). This system makes sense in as long as you don't violate the ODR.
Now in your case the linker chooses to use the first instantiation it gets, which leads to the result being dependent on the order of linking.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With