Почему нельзя помещать определение шаблона в cpp файл



Что такое шаблон? Когда я был маленьким и тупеньким, мне объясняли концепцию объектов и классов вот так: класс - это чертеж корабля, а объект - построенный по этому чертежу конкретный корабль. Раньше мне критически не хватало такого объяснения, хотя сейчас это кажется интуитивно понятным. Суть в том, что класс - это не просто чертеж, а очень подробный чертеж со всеми мерками, подробными схемами работы всех отдельных частей, и схемами взаимодействия этих частей друг с другом. То есть какой-то сверхразум(компилятор) посмотрев на все это добро, может четко понять о будущем корабле буквально все и в соло построить его абсолютно рабочую версию, каждая деталь которого будет совпадать с планом.



Так вот шаблон класса - это похожий на класс чертеж, почти такой же подробный, только в этом чертеже практически нет описания каких-то отдельных частей. Только такие же подробные схемы взаимодействия этих отсутсвующих частей с другими частями корабля. Да, мы можем наложить какие-то ограничения на эти неизвестные части(sfinae, концепты), но подробного описания все равно не будет.



Так вот, чтобы получить этот полный подробный чертеж(класс), нужно добавить эти подробные схемы отдельных частей. То есть инстанцировать шаблон.



И пока этого не сделано, ни один сверхразум не сможет вам построить по неполной схеме готовый полноценный корабль.



И вот все сверхразумы мира договорились, что если они все равно не могут построить полноценный корабль, то даже если они получат чертеж, в котором будет описано все-превсе кроме одной детали, они даже не будут начинать строить этот корабль. А вдруг схему детали так и не завезут. Зачем стараться зря?



Вроде мы разобрались с аналогиями, перейдем в реальность.



Все рассуждения выше и ниже актуальны также и для других шаблонных сущностей. Это так, предвижу возможные вопросы.



Возьмем стандартную схему объявления шаблонного класса в хэдэре и ее реализацию в файле исходников.





// ship.hpp

template<typename T>

struct Ship

{

// contain some fields

void TurnShip(T command);

};



// ship.cpp

#include "ship.hpp"



template <class T>

void Ship<T>::TurnShip(T command) {/* do stuff using command */}



// main.cpp

#include "ship.hpp"



int main() {

Ship<int> ship;

ship.TurnShip(5);

}





Если бы это был обычный класс, тут бы все прокатило. Но здесь шаблон и это все меняет.



В чем прикол. В единице трансляции, соответствующей файлу ship.cpp, не будет сгенерировано никакого кода. Потому что в этой единице трансляции не предоставлено полной схемы типа T. И поэтому компилятор просто ничего не будет генерировать из этого шаблона.



А вы в main.cpp пытаетесь использовать уже сам объект. То есть полностью готовый корабль из полноценного чертежа. В файле main.cpp нет никакого чертежа. Ну хорошо, возможно там что-то в ship.hpp есть подобное. А там только объявление шаблона. Ну хорошо, компилятор на данном этапе может работать только с объявлением, как и с объявлениями обычных классов, ничего странного. Сейчас он поставит заглушки на моменты создания и использования объекта. Но он попытается на этапе линковки разрезолвить все символы. Но вот незадача - на этапе линковки компилятор не увидит сгенерированного кода специализации Ship<int>. Потому что в единице трансляции, соответствующей файлу ship.cpp, никакого полезного сгенерированного кода нет!



Решение тут простое - вынести определение шаблона в хэдэр и подключать его везде, чтобы компилятор уже на этапе компиляции видел это определение и смог сам инстанцировать подходящую специализацию.



Однако есть и другое решение!



Можно оставить определение в цппшнике, но нужно добавить немного магии. Знающих прошу не спойлерить в комментах, хочу оставить интригу до завтра)



Don't do extra work in vain. Stay cool.



#cppcore #template