header.h:
#include <iostream>
template<typename T>
class Test {
public:
__attribute__((noinline)) void fun(int a, int b) {
std::cout << a + b << std::endl;
}
};
class SpecialClass {};
a.cpp:
#include "header.h"
int main() {
Test<SpecialClass> test;
test.fun(10, 8);
return 0;
}
b.cpp:
#include "header.h"
template<>
void Test<SpecialClass>::fun(int a, int b) {
std::cout << a - b << std::endl;
}
g++ version: 8.3.1
Compile this program without optimization:
$ g++ -O0 a.cpp b.cpp -o pro_O0
$ ./pro_O0
2
The output is 2, which means the specilized version is called.
Compile this program using -O3:
$ g++ -O3 a.cpp b.cpp -o pro_O3
$ ./pro_O3
18
The specilized version is NOT used in this case.
I checked the symbol in object file:
When using -O0:
$ nm -C a_O0.o | grep fun
0000000000000000 W Test<SpecialClass>::fun(int, int)
$ nm -C b_O0.o | grep fun
0000000000000000 T Test<SpecialClass>::fun(int, int)
Test<SpecialClass>::fun(int, int) in a.o is a weak symbol, so it is override by the symbol in b.o at link time.
When using -O3:
$ nm -C a_O3.o | grep fun
0000000000000000 t Test<SpecialClass>::fun(int, int) [clone .isra.1] [clone .constprop.2]
$ nm -C b_O3.o | grep fun
0000000000000000 T Test<SpecialClass>::fun(int, int)
Test<SpecialClass>::fun(int, int) in a.o become a local symbol, which can not be override by the symbol in b.o
So I wonder
- Is this a undefined behavior? If it is, what’s the related standard rules?
- How to solve this problem if I want to always use the specialized version? Except putting them in a single file, I known this indeed take effect, but my real program is much more complex and difficult to do large refactor.
I have tried add __attribute__((weak)), it seems has no effect on this case, but works on my real program.
>Solution :
Is this a undefined behavior? If it is, what’s the related standard rules?
Yes, it is ill-formed, no diagnostic required (IFNDR), effectively meaning the same as undefined behavior.
[temp.expl.spec]/7 from the post-C++20 standard draft N4868:
If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.
In your translation unit for a.cpp there is no declaration of the explicit specialization at all, but the call to fun causes implicit instantiation of the specialization.
How to solve this problem if I want to always use the specialized version? Except putting them in a single file, I known this indeed take effect, but my real program is much more complex and difficult to do large refactor.
By declaring (but not defining) the explicit specialization in the header file:
template<typename T>
class Test {
public:
__attribute__((noinline)) void fun(int a, int b) {
std::cout << a + b << std::endl;
}
};
class SpecialClass {};
// explicit specialization declaration
template<>
void Test<SpecialClass>::fun(int a, int b);
The explicit specialization definition can then stay in b.cpp.