You can define template methods in .cpp files without explicit instantiation, as long as they are private
A typical nuisance in C++ is that the bodies of template functions have to be in headers, causing annoyingly large recompilations after small changes, both by requiring more includes and by being themselves included by many files.
This is generally true. In order to use a template function, its full definition must be available, unlike normal functions that only need to be declared. The reason is well known – a template function is not really a batch of assembly instructions with an address like other functions, it is a compile time abstraction that can be turned into a function when its template arguments are specialised (or deduced); this prevents it from being used by compilation units that don’t have access to its full definition.
Now, does that mean it necessarily has to be defined in a header? Consider this snippet:
// header class CensoringPrinter { std::vector<std::string> forbiddenWords = {"Satan", "666", "666.666"}; template <typename Printed> void printInternal(const Printed& printed) { std::string converted = std::to_string(printed); for (auto& it : forbiddenWords) { if (converted == it) return; } std::cout << converted << std::endl; } public: CensoringPrinter(std::span<const std::string> forbidden); void print(double num); void print(int64_t num); void print(uint64_t num); }; // source CensoringPrinter::CensoringPrinter(std::span<const std::string> forbidden) : forbiddenWords(forbidden) { } void CensoringPrinter::print(double num) { if (num != num) { // don't print NaN return; } printInternal(num); } void CensoringPrinter::print(int64_t num) { printInternal(num); } void CensoringPrinter::print(uint64_t num) { if (num == uint64_t(-1)) { // clear integer overflow throw std::logic_error("Printing an overflown value"); } printInternal(num); }
The example above might be somewhat artificial, but it shows a particular case – to avoid all duplication (which might be larger in a better example), there has to be a template function printInternal
. And one that has an additional dependency (std::cout
) that could otherwise be included only in the source (if it was a dependency needed by the class, it could be forward declared without this template).
Because it is a private method used only by other methods of the class, it’s not really needed by other classes. It’s tempting to convert it into a free function in the source:
template <typename Printed> void printInternal(const std::vector<std::string>& forbiddenWords, const Printed& printed) { std::string converted = std::to_string(printed); for (auto& it : forbiddenWords) { if (converted == it) return; } std::cout << converted << std::endl; }
This makes calling it more verbose and can get ugly if it uses more member variables of the class, but it does the trick. Another way would be to explicitly instantiate it for every type it will be used with, but that won’t work with lambdas and such.
But… is it necessary?
The definition of the template function must be available wherever the template function is used. However, if it is a private method, then it’s not meant to be used outside of the definitions of the class’ methods. So it should not matter that the definition of the function will be available only in one source file!
// header class CensoringPrinter { std::vector<std::string> forbiddenWords = {"Satan", "666", "666.666"}; template <typename Printed> void printInternal(const Printed& printed); public: CensoringPrinter(std::span<const std::string> forbidden); void print(double num); void print(int64_t num); void print(uint64_t num); }; // source template <typename Printed> void CensoringPrinter::printInternal(const Printed& printed) { std::string converted = std::to_string(printed); for (auto& it : forbiddenWords) { if (converted == it) return; } std::cout << converted << std::endl; } CensoringPrinter::CensoringPrinter(std::span<const std::string> forbidden) : forbiddenWords(forbidden) { } void CensoringPrinter::print(double num) { if (std::isnan(num)) { // don't print NaN return; } printInternal(num); } void CensoringPrinter::print(int64_t num) { printInternal(num); } void CensoringPrinter::print(uint64_t num) { if (num == uint64_t(-1)) { // clear integer overflow throw std::logic_error("Printing an overflown value"); } printInternal(num); }
I’ve been programming in C++ for years until I realised this can work. I know people who programmed in C++ for decades without realising it.
One possible surprise that can appear here is that unlike with normal methods, the restriction that template function’s definition must be written before its first use still applies. So it typically has to be defined on top of the source file (or explicitly instantiated for every type, kinda defeating the purpose).
Happy coding and hopefully you’ll benefit from this small piece of trivia.
It’s not only for private ones. You can even have a public template func where the def is in cpp and you explicit instantiate it for few types. These are the only types that can be used. The linker will find the function in the .o (compilation unit) w/o issues but if you try to use it with types not explicit instantiated, then you’ll get a linker error.
The most important thing is to understand how template works wrt compilation and linker process. Google my name under https://vorbrodt.blog/ and you can see one post about this.
thanks for the great post!