Проблема в том, что шаблон функции не является функцией. Это шаблон для создания необходимых функций.
Таким образом, для работы шаблона компилятору интуитивно нужны две части информации: сам шаблон и тип, который следует подставить в него.
Это не похоже на вызов функции, который компилятор может сгенерировать, как только узнает, что функция существует. Ему не нужно знать, что делает функция, просто она выглядит как void Frobnicate(int, float)
или какова ее подпись.
Когда вы объявляете шаблон функции, не определяя его, вы только сообщаете компилятору, что такой шаблон существует, а не как он выглядит. Этого недостаточно для того, чтобы компилятор мог создать его экземпляр, он также должен видеть полное определение. Обычное решение состоит в том, чтобы поместить весь шаблон в заголовок, который может быть включен при необходимости.