Ответ 1
Это потому, что определение статического элемента данных является самим шаблоном. Разрешение этого необходимо по той же причине, что вам разрешено иметь шаблон функции, который не встроен несколько раз в программу. Вам нужен шаблон для создания результирующего объекта (скажем, функции или статического члена данных). Если вам не удастся установить определение статического элемента данных, как бы вы создавали экземпляр следующих
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
Это означает, что на самом деле он удаляет все, кроме одного экземпляра.