In C++, templates are a powerful feature that allows for generic programming. However, template implementation can be tricky due to the way they are instantiated by the compiler. In this tutorial, we will explore why template implementations must be visible to the compiler when instantiating them.
Introduction to Template Instantiation
Template instantiation is the process of creating a concrete class or function from a template definition. When you use a template with a specific type, the compiler generates a new version of the template code for that type. For example:
template <typename T>
struct MyClass {
void doSomething(T param) { /* implementation */ }
};
// Instantiating MyClass with int
MyClass<int> myIntClass;
In this case, the compiler generates a new class MyClass<int>
with an int
parameter.
The Need for Visible Template Implementation
The key point to understand is that template instantiation requires the compiler to have access to the implementation of the template. This means that the implementation must be visible in the same compilation unit where the template is instantiated.
Consider a scenario where we define a template class in a header file (myclass.h
) and implement it in a separate source file (myclass.cpp
):
// myclass.h
template <typename T>
struct MyClass {
void doSomething(T param);
};
// myclass.cpp
#include "myclass.h"
template <typename T>
void MyClass<T>::doSomething(T param) { /* implementation */ }
Now, if we try to use MyClass
with a specific type in another source file (main.cpp
), the compiler will not be able to find the implementation of doSomething
:
// main.cpp
#include "myclass.h"
int main() {
MyClass<int> myIntClass;
myIntClass.doSomething(10); // Error: doSomething not defined
}
The reason for this error is that the compiler does not have access to the implementation of doSomething
when compiling main.cpp
.
Solutions to Template Instantiation
There are two common solutions to this problem:
-
Include the implementation in the header file: By including the implementation in the header file, we make it visible to the compiler when instantiating the template.
// myclass.h
template
struct MyClass {
void doSomething(T param) { /* implementation */ }
};
2. **Use explicit instantiation**: We can explicitly instantiate the template for specific types in a separate source file. This way, we can keep the implementation separate from the declaration.
```cpp
// myclass.h
template <typename T>
struct MyClass {
void doSomething(T param);
};
// myclass.cpp
#include "myclass.h"
template <typename T>
void MyClass<T>::doSomething(T param) { /* implementation */ }
// Explicit instantiation for int and float
template class MyClass<int>;
template class MyClass<float>;
// main.cpp
int main() {
MyClass<int> myIntClass;
myIntClass.doSomething(10); // Okay, explicitly instantiated
}
In summary, template implementation must be visible to the compiler when instantiating them. This can be achieved by including the implementation in the header file or using explicit instantiation.
Best Practices
When working with templates, follow these best practices:
- Keep the implementation separate from the declaration whenever possible.
- Use explicit instantiation for specific types if you need to keep the implementation separate.
- Be aware of the trade-offs between compilation time and code organization when deciding where to place template implementations.
By following these guidelines and understanding how template instantiation works, you can write more effective and maintainable C++ code using templates.