Это потому, что определение статического члена данных само является шаблоном. Это необходимо по той же причине, по которой вам разрешено иметь шаблон функции, который несколько раз не встроен в программу. Вам нужен шаблон для генерации результирующей сущности (скажем, функции или статического члена данных). Если вам не разрешено помещать определение статического члена данных, как бы вы описали следующее
template<typename T>
struct F {
static int const value;
};
template<typename T>
int const F<T>::value = sizeof(T);
Неизвестно, что такое T
- Стандарт говорит, что определение вне шаблона класса является определением шаблона, в котором параметры наследуются от его владельца шаблона класса.
Я провел некоторый эксперимент с GCC. Далее мы имеем одно неявное создание экземпляра F<float>::value
и одну явную специализацию F<char>::value
, которая должна быть определена в файле .cpp, чтобы не вызывать дублированных ошибок символов при многократном включении.
// Translation Unit 1
template<typename T>
struct F {
static int value;
};
template<typename T>
int F<T>::value = sizeof(T);
// this would belong into a .cpp file
template<> int F<char>::value = 2;
// this implicitly instantiates F<float>::value
int test = F<float>::value;
int main() { }
Второй блок перевода содержит просто еще одно неявное создание того же элемента статических данных
template<typename T>
struct F {
static int value;
};
template<typename T>
int F<T>::value = sizeof(T);
int test1 = F<float>::value;
Вот что мы получаем с GCC - он превращает каждое неявное создание экземпляра в слабый символ и вставляет его в отдельный раздел. Слабые символы не вызовут ошибок, если их будет несколько во время ссылки. Вместо этого компоновщик выберет один экземпляр и отбросит остальные, предполагая, что все они одинаковы
objdump -Ct main1.o # =>
# cut down to the important ones
00000000 l df *ABS* 00000000 main1.cpp
0000000a l F .text 0000001e __static_initialization_and_destruction_0(int, int)
00000000 l d .data._ZN1FIfE5valueE 00000000 .data._ZN1FIfE5valueE
00000028 l F .text 0000001c global constructors keyed to _ZN1FIcE5valueE
00000000 g O .data 00000004 F<char>::value
00000000 g O .bss 00000004 test
00000000 g F .text 0000000a main
00000000 w O .data._ZN1FIfE5valueE 00000004 F<float>::value
Итак, как мы видим, F<float>::value
- это слабый символ, который означает, что компоновщик может видеть несколько из них во время соединения. test
, main
и F<char>::value
являются глобальными (не слабыми) символами. Связав main1.o
и main2.o
вместе, мы видим в выводе карты (-Wl,-M
) следующее
# (mangled name)
.data._ZN1FIfE5valueE
0x080497ac 0x4 main1.o
0x080497ac F<float>::value
Это означает, что на самом деле он отбрасывает все, кроме одного экземпляра.