C++ has evolved over the last decade, and what you described was a common hack that is no longer necessary. This (and several alternative approaches) was used with older C++ standards by using a combination of two kinds of sub-optimal practices: defining the actual template in a standalone translation unit and explicitly instantiating the template methods (including its constructor) in that translation unit, and by relying on the fact that, otherwise, templates can only be effectively implemented in a header file. This effectively achieves that goal by producing a cryptic link failure.
Fortunately, that hack is not needed in modern C++ any more. As I mentioned, C++ has evolved to the point where something like that can be specified cleanly, explicitly, and in a crystal clear way using C++20 constraints:
#include <type_traits>
template<typename T>
requires std::is_same_v<T, int> || std::is_same_v<T, char>
struct my_template {};
my_template<int> a; // OK
my_template<char> b; // OK
my_template<char *> c; // ERROR
Your actual template can be defined and implemented normally, in a header file, and the constraint clearly imposes a restriction that your template be instantiated only with specific types.