Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Templates in Multiple Files
Organize template definitions in headers for use across translation units.
Templates in Multiple Files
When working with regular functions, you typically separate declarations (in header files) from definitions (in source files). But templates require special handling.
Why Templates Are Different
Consider this attempt to use a template across multiple files:
calculation.h:
#pragma once
template <typename T>
T doubleValue(T number);
calculation.cpp:
template <typename T>
T doubleValue(T number)
{
return number * 2;
}
main.cpp:
#include "calculation.h"
#include <iostream>
int main()
{
std::cout << doubleValue(5) << '\n';
std::cout << doubleValue(3.7) << '\n';
return 0;
}
This looks reasonable, but it won't link. You'll get errors like:
undefined reference to `int doubleValue<int>(int)`
undefined reference to `double doubleValue<double>(double)`
Understanding the Problem
When the compiler processes main.cpp:
- It sees the forward declaration of
doubleValuefrom the header - It encounters calls to
doubleValue<int>anddoubleValue<double> - It needs the template definition to instantiate these functions
- It doesn't have the definition, so it assumes they'll be linked later
When the compiler processes calculation.cpp:
- It sees the template definition
- There are no calls to the template in this file
- The compiler doesn't instantiate any functions
Result: The linker can't find the instantiated functions because they were never created.
Solution: Put Templates in Headers
The standard solution is to put the entire template definition in the header file:
calculation.h:
#pragma once
template <typename T>
T doubleValue(T number)
{
return number * 2;
}
main.cpp:
#include "calculation.h"
#include <iostream>
int main()
{
std::cout << doubleValue(5) << '\n';
std::cout << doubleValue(3.7) << '\n';
return 0;
}
Now when the compiler processes main.cpp, it has the complete template definition available. It can instantiate doubleValue<int> and doubleValue<double> right there.
Templates need their full definitions visible at the point of use. This is why template code typically lives entirely in header files.
What About Code Duplication?
You might wonder: if multiple source files include the same template header, won't the compiler generate duplicate functions?
Yes and no. The compiler will instantiate the template in each translation unit that uses it. However, template instantiations are implicitly inline, which tells the linker that duplicate definitions are okay—it keeps one copy and discards the rest.
Organizing Larger Templates
For complex templates with many functions, putting everything in one header can be unwieldy. A common pattern uses .tpp or .inl files for implementations:
buffer.h:
#pragma once
template <typename T, int Size>
class CircularBuffer
{
public:
void push(T value);
T pop();
bool isEmpty() const;
private:
T data[Size]{};
int head{};
int tail{};
int count{};
};
#include "buffer.tpp" // Include implementation at end
buffer.tpp:
template <typename T, int Size>
void CircularBuffer<T, Size>::push(T value)
{
data[tail] = value;
tail = (tail + 1) % Size;
++count;
}
template <typename T, int Size>
T CircularBuffer<T, Size>::pop()
{
T value{ data[head] };
head = (head + 1) % Size;
--count;
return value;
}
template <typename T, int Size>
bool CircularBuffer<T, Size>::isEmpty() const
{
return count == 0;
}
main.cpp:
#include "buffer.h"
int main()
{
CircularBuffer<int, 5> buffer{};
buffer.push(10);
buffer.push(20);
return 0;
}
The .tpp file contains the implementation details, but it's included by the header, so everything is still available at the point of use.
Alternative: Explicit Instantiation (Advanced)
For certain specialized use cases, you can explicitly instantiate specific template versions:
calculation.cpp:
template <typename T>
T doubleValue(T number)
{
return number * 2;
}
// Explicitly instantiate specific versions
template int doubleValue<int>(int);
template double doubleValue<double>(double);
This tells the compiler to generate these specific versions in this translation unit. Other files can use forward declarations to reference them.
However, this approach has downsides:
- You must know all needed instantiations in advance
- Adding new types requires updating the explicit instantiation list
- It's fragile and error-prone
For function templates: Put the entire definition in the header file.
For large class templates: Consider using a separate
.tpp or .inl file for implementations, but include it at the end of the header.Avoid: Separating templates across
.h and .cpp files without explicit instantiation.
This approach might feel wasteful compared to regular functions, but it's necessary for templates to work correctly. The compiler needs the complete template definition to generate code for each type you use it with.
Summary
Templates require special consideration when organizing code across multiple files:
The Problem:
- Templates need full definitions to instantiate
- Separating declaration (.h) and definition (.cpp) causes linker errors
- The compiler can't instantiate what it can't see
Solutions:
- Put templates in headers - Most common and recommended approach
- Use .tpp/.inl files - For organization, include at end of header
- Explicit instantiation - Advanced, fragile, avoid unless necessary
Why It Works:
- Template instantiations are implicitly
inline - Linker handles duplicate definitions automatically
- Each translation unit gets what it needs
Key Rules:
- Template definitions must be visible at point of use
- Header-only is the standard approach for templates
- Don't split templates across .h and .cpp files
Templates in Multiple Files - Quiz
Test your understanding of the lesson.
Practice Exercises
Using Function Templates in Multiple Files
Learn why templates must be defined in header files.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!