Question
In C++, I have template code that I would prefer to place in a .cpp file instead of defining inline in the header. I understand that this can work if I know in advance which template types will be used.
For example, suppose I have:
// foo.h
#include <string>
class foo
{
public:
template <typename T>
void doSomething(const T& t);
};
// foo.cpp
#include "foo.h"
template <typename T>
void foo::doSomething(const T& t)
{
// Do something with t
}
template void foo::doSomething<int>(const int&);
template void foo::doSomething<std::string>(const std::string&);
The last two lines explicitly instantiate the template for int and std::string, so the program links as long as those are the only types used.
Is this considered a bad workaround, or is it a standard and portable technique that works across other compilers and linkers? I am currently using Visual Studio 2008, but I want the code to be portable to other environments later.
Short Answer
By the end of this page, you will understand why C++ templates are usually defined in headers, when it is valid to move template definitions into a .cpp file, and how explicit instantiation makes that possible. You will also see why this is a standard C++ technique rather than a compiler-specific hack, along with its trade-offs and common pitfalls.
Concept
In C++, templates are not normal functions or classes. A template is more like a blueprint that the compiler uses to generate actual code when a specific type is needed.
For example, if you write:
template <typename T>
void f(T value) { }
then f<int> and f<std::string> are only generated when the compiler sees they are needed.
Why templates are usually put in headers
The compiler must usually see the full template definition at the point where it tries to instantiate it. That is why template code is commonly placed in header files.
If only the declaration is visible:
template <typename T>
void f(T value);
and the definition is hidden away in a .cpp file, another translation unit may try to use f<double> without having enough information to generate it.
The exception: explicit instantiation
If you already know exactly which template types will be used, you can place the definition in a .cpp file and explicitly tell the compiler to generate those versions there:
Mental Model
Think of a template like a cookie cutter design.
- The header gives other files the shape of the cutter.
- The definition gives the full cutter design.
- Instantiation is when you actually stamp out a cookie of a specific shape and size.
If another .cpp file wants an int cookie, the compiler must either:
- have the full cutter design available right there, or
- be told that someone else already made that exact cookie
Putting template definitions in headers means every file can stamp out whatever cookie it needs.
Putting the definition in a .cpp file with explicit instantiations means:
- only specific cookies are manufactured
- everyone else can only request those exact ones
So this is not a trick. It is more like switching from made-to-order to pre-manufactured inventory.
Syntax and Examples
The core syntax looks like this:
// header
class Foo {
public:
template <typename T>
void doSomething(const T& value);
};
// source file
#include "Foo.h"
#include <string>
#include <iostream>
template <typename T>
void Foo::doSomething(const T& value) {
std::cout << value << '\n';
}
// Explicit instantiations
template void Foo::doSomething<int>(const int&);
template void Foo::doSomething<std::string>(const std::string&);
Now this usage works:
Step by Step Execution
Consider this code:
// Foo.h
class Foo {
public:
template <typename T>
void print(const T& value);
};
// Foo.cpp
#include "Foo.h"
#include <iostream>
#include <string>
template <typename T>
void Foo::print(const T& value) {
std::cout << value << '\n';
}
template void Foo::print<int>(const int&);
template void Foo::print<std::string>(const std::string&);
// main.cpp
#include
{
Foo f;
f.();
f.(std::());
}
Real World Use Cases
Explicit template instantiation is useful in several practical situations.
1. Library code with a fixed supported type set
A math or utility library may support only a few types:
intfloatdouble
Instead of exposing template definitions in headers, the library can instantiate only those supported types.
2. Reducing compile times
Header-only templates can slow builds because every translation unit may instantiate the same templates repeatedly. Moving definitions into a .cpp file and explicitly instantiating common types can reduce compile overhead.
3. Hiding implementation details
A library author may want users to see only the public API, not the full template implementation. Explicit instantiation allows some template code to stay in source files.
4. Controlling binary size
If only a few types are valid, generating code only for those types can keep the binary more predictable.
5. Enforcing allowed types
Sometimes a template is written in a generic style internally, but the public API should support only selected types. Explicit instantiation can act as a practical boundary.
For example, a serializer might support only:
intstd::stringstd::vector<int>
Real Codebase Usage
In real codebases, developers usually choose one of two patterns.
Pattern 1: Header-only templates
This is the most common pattern when a template should work for arbitrary types.
// all in header
template <typename T>
T add(T a, T b) {
return a + b;
}
Use this when:
- the template is meant to be truly generic
- users may instantiate it with unknown types
- ease of use matters more than hiding implementation
Pattern 2: .cpp definition plus explicit instantiation
This is common in libraries that support a fixed set of types.
// implementation in .cpp
// explicit instantiations for supported types only
Use this when:
- the supported types are known in advance
- compile-time cost matters
- implementation hiding is useful
Related real-world patterns
Validation by limiting supported types
Instead of allowing every possible type, a codebase may intentionally instantiate only approved ones.
Error handling through linker failures or static assertions
If unsupported types should fail clearly, explicit instantiation can be combined with constraints or static checks.
Common Mistakes
1. Assuming declarations are enough for any type
Beginners often think this is sufficient:
// Foo.h
class Foo {
public:
template <typename T>
void print(const T& value);
};
// Foo.cpp
template <typename T>
void Foo::print(const T& value) {
}
This compiles, but usage from another source file may fail at link time because no instantiation was generated.
2. Forgetting to explicitly instantiate needed types
Broken example:
// Only this exists
template void Foo::print<int>(const int&);
But elsewhere:
Foo f;
f.print(std::string("hello"));
Comparisons
| Approach | Where definition lives | Supports unknown types? | Compile-time cost | Hides implementation? | Typical use |
|---|---|---|---|---|---|
| Header-only template | Header | Yes | Higher | No | General-purpose templates |
.cpp + explicit instantiation | Source file | No | Lower for users | Yes | Fixed supported type set |
| Non-template overloads | Header/source | No | Usually lower | Yes | Small fixed APIs |
Explicit instantiation vs header-only templates
- Header-only is flexible.
- is controlled.
Cheat Sheet
// Header
class Foo {
public:
template <typename T>
void func(const T& value);
};
// Source
template <typename T>
void Foo::func(const T& value) {
// implementation
}
template void Foo::func<int>(const int&);
template void Foo::func<std::string>(const std::string&);
Rules to remember
- Templates are usually defined in headers.
- A template definition in a
.cppfile works only if needed instantiations are provided. - Explicit instantiation is standard C++ and portable.
- Unsupported types usually cause linker errors, not compile errors.
- Use this approach when the supported type set is fixed.
- If the template should work for arbitrary user-defined types, keep the definition in the header.
Quick decision guide
FAQ
Is putting a C++ template definition in a .cpp file portable?
Yes. If you use explicit instantiation correctly, this is standard C++ and works across conforming compilers.
Why are templates usually defined in header files?
Because the compiler normally needs the full template definition wherever it instantiates the template for a new type.
Is explicit instantiation a hack?
No. It is an official language feature and a common technique when only specific types are supported.
What happens if I use a type that was not explicitly instantiated?
You will usually get a linker error because no compiled definition exists for that type.
Can I use this technique for template classes too?
Yes. Explicit instantiation also works for class templates.
Should I always move templates into .cpp files?
No. Most templates are better kept in headers unless you control the full list of supported types.
When should I use overloads instead of templates?
If you only support a very small fixed set of types, overloads can be simpler and clearer.
Does this help reduce compile times?
It can, especially in larger projects, because fewer translation units need to instantiate the same templates repeatedly.
Mini Project
Description
Build a small C++ logging utility that supports printing only selected types using explicit template instantiation. This demonstrates how to keep a template definition in a .cpp file while still exposing a clean interface in a header.
Goal
Create a logger class whose template member function works for int and std::string, but not for unsupported types unless you explicitly add them.
Requirements
- Create a class with a templated member function declared in a header file.
- Define the templated member function in a
.cppfile. - Explicitly instantiate the function for
intandstd::string. - Show successful calls with supported types in
main(). - Keep the implementation separate from the declaration.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.