Явная и неявная инстанциация шаблона
Как только компилятор видит полное определение шаблона, он может инстанцировать его с каким-то конкретным аргументом. И тут есть 2 варианта.
Вернемся к вчерашнему примеру с шаблоном корабля.
Согласно заветам предыдущего поста, мы перенесли все определение шаблона в хэдэр, подключили этот хэдэр в мэйн и использовали объект. И раз в мэйне мы используем объект, то в этом конкретном случае произошло неявное инстанцирование шаблона - компилятор все сделал за нас. Мы дали ему определение шаблона, укропу, кошачью жопу, ... и охапку дров, а он нам выдал плов. Точнее конкретный код, соответствующий конкретной специализации Ship<int>.
Но вы уже поняли, да? Раз есть неявное инстанцирование, то есть и явное! То есть, мы сами своими ручками-закарючками(осуждаю боди-шейминг, говорю про себя) можем сказать компилятору, что мы хотим, чтобы он инстанцировал нужную нам специализации и сгенерировал нам для нее код. И эта штука поможет нам решить проблему с определением шаблонов в цппшниках.
Представим себе, что наш корабль принимает команды только в текстовом виде. И на данный момент никаких других видов команд не предусмотрено. Тогда единственная планируемая специализация шаблона Ship будет со строками. В таком случае, мы можем заставить компилятор инстанцировать нужный нам шаблон в единице трансляции с его определением и тогда на этапе линковки компилятор сможет разрезолвить все символы и сгенерировать полноценный бинарник без ошибок. Для этого нужно добавить лишь одну строчку:
template class Ship<std::string> - яное инстанцирование шаблона. Синтаксис следующий:
template class-key template-name <argument-list>;
class-key - любое из struct/class/union, должно соответствовать оному в самом шаблоне.
Как видите, у этой прекрасной фичи есть ограничения. Если вы используете шаблон с большим количеством различных специализаций, то вам придется каждую из них указывать явно в ццпшнике с определением шаблона. Это немного уменьшает гибкость изменений.
А также никакой внешний код, который вы не можете трогать и представляете его как черный ящик, не сможет использовать ваш шаблон. Потому что вы не знаете, какие там специализации используются, а значит не сможете добавить его в цппшник с определением шаблона.
Но в целом, это хорошая практика. Поэтому используйте на здоровье.
Важно помнить, что явно инстанцировать шаблон можно всего раз во всей программе. Помните об этом, когда соберетесь поместить эту строчку в хэдэр.
Find a way out of your problems. Stay cool.
#cppcore #template
Как только компилятор видит полное определение шаблона, он может инстанцировать его с каким-то конкретным аргументом. И тут есть 2 варианта.
Вернемся к вчерашнему примеру с шаблоном корабля.
// ship.hpp
template<typename T>
struct Ship
{
// contain some fields
void TurnShip(T command) {// do some stuff}
};
// main.cpp
#include "ship.hpp"
int main() {
Ship<int> ship;
ship.TurnShip(5);
}
Согласно заветам предыдущего поста, мы перенесли все определение шаблона в хэдэр, подключили этот хэдэр в мэйн и использовали объект. И раз в мэйне мы используем объект, то в этом конкретном случае произошло неявное инстанцирование шаблона - компилятор все сделал за нас. Мы дали ему определение шаблона, укропу, кошачью жопу, ... и охапку дров, а он нам выдал плов. Точнее конкретный код, соответствующий конкретной специализации Ship<int>.
Но вы уже поняли, да? Раз есть неявное инстанцирование, то есть и явное! То есть, мы сами своими ручками-закарючками(осуждаю боди-шейминг, говорю про себя) можем сказать компилятору, что мы хотим, чтобы он инстанцировал нужную нам специализации и сгенерировал нам для нее код. И эта штука поможет нам решить проблему с определением шаблонов в цппшниках.
Представим себе, что наш корабль принимает команды только в текстовом виде. И на данный момент никаких других видов команд не предусмотрено. Тогда единственная планируемая специализация шаблона Ship будет со строками. В таком случае, мы можем заставить компилятор инстанцировать нужный нам шаблон в единице трансляции с его определением и тогда на этапе линковки компилятор сможет разрезолвить все символы и сгенерировать полноценный бинарник без ошибок. Для этого нужно добавить лишь одну строчку:
// ship.hpp
template<typename T>
struct Ship
{
// contain some fields
void TurnShip(T command);
};
// ship.cpp
#include "ship.hpp"
#include <string>
template <class T>
void Ship<T>::TurnShip(T command) {/* do stuff using command */}
template struct Ship<std::string>; // HERE IT IS!!
// main.cpp
#include "ship.hpp"
#include <string>
int main() {
Ship<std::string> ship;
ship.TurnShip(std::string{"Turn upside down"});
}
template class Ship<std::string> - яное инстанцирование шаблона. Синтаксис следующий:
template class-key template-name <argument-list>;
class-key - любое из struct/class/union, должно соответствовать оному в самом шаблоне.
Как видите, у этой прекрасной фичи есть ограничения. Если вы используете шаблон с большим количеством различных специализаций, то вам придется каждую из них указывать явно в ццпшнике с определением шаблона. Это немного уменьшает гибкость изменений.
А также никакой внешний код, который вы не можете трогать и представляете его как черный ящик, не сможет использовать ваш шаблон. Потому что вы не знаете, какие там специализации используются, а значит не сможете добавить его в цппшник с определением шаблона.
Но в целом, это хорошая практика. Поэтому используйте на здоровье.
Важно помнить, что явно инстанцировать шаблон можно всего раз во всей программе. Помните об этом, когда соберетесь поместить эту строчку в хэдэр.
Find a way out of your problems. Stay cool.
#cppcore #template