When the compiler is instantiating a template class, it needs to have access to the implementation of the template class in order to generate the code. This means that you can’t put the declaration of a template class in a header file, and the implementation in a cpp file, and then just include the header file in client code. The linker won’t be able to find the implementation while it’s instantiating the template, and so will generate errors.
This is why template libraries like the STL and Boost are traditionally implemented as “header-only” libraries, with all of the implementation included inline in the header files. A related solution is to put the implementation code in a separate file (possibly with a “.tpp” extension) and then include that at the end of the header file. The disadvantage of these methods is that template instantiations cannot be shared between compilation units, and this causes code bloat.
You can keep the implementations separate by explicitly intantiating the templates for the types you want in the template class cpp file. This means that the implementation code is visible when the templates are instantiated, but this instantiation will only happen once, so code bloat is avoided. This is the approach taken by many compiled libraries. It means though, that only those explicit instantiations are available to clients.