C++ Part I · Module 18 · Templates and Generic Programming
Templates in Headers and the Errors They Produce
Two practical consequences follow from the previous chapter, and both are things people meet on their first real template rather than in a tutorial. The first is a placement rule that looks like an arbitrary convention and is actually forced. If a template must be instantiated wherever it is used, and instantiation requires the definition, then a definition sitting in a source file is invisible to every other source file — and the failure arrives at link time, as a missing symbol for a function that was never generated, rather than as a compile error pointing at anything useful. This chapter measures that across five configurations: the broken one, the two repairs, the repair applied incompletely, and the question of whether putting the same template in two translation units violates the one-definition rule, which it does not. The second consequence is the one templates are notorious for. A failed instantiation deep inside the standard library produces a diagnostic this chapter measures at three hundred and seventy lines, containing twenty errors and sixty-four notes, whose first error is inside a header you have never opened. That is genuinely hard to read until you know that exactly one line in it names your mistake, and which one — after which it becomes routine, and the comparison with a simple template's seven-line failure explains where the length actually comes from.
Advanced19 min readC++Part IFundamentalsTemplates
Part I · Module 18 · Chapter 18.7 · Templates in Headers and the Errors They Produce
1. The Rule
A template's definition must be visible in every translation unit that instantiates it.
In practice: template definitions go in headers. Not just the declarations — the bodies.
That is not a convention. It follows from Chapter 18.6 §1: instantiation generates code from the definition, it happens in the translation unit that uses the template, and a compiler cannot generate code from a definition it cannot see.
2. What Happens Otherwise
The ordinary-function habit, applied to a template:
template <typename T> T twice(T v); // declaration only #include "tmpl.h"
template <typename T> T twice(T v) { return v + v; } // definition here #include "tmpl.h"
int main() { twice(21); twice(1.5); }A. definition in a .cpp, used from another TU:
link exit: 1
Undefined symbols for architecture arm64:
clang++: error: linker command failed with exit code 1It compiles and fails to link. Both translation units are individually valid: main.cpp saw a declaration and emitted calls to twice<int> and twice<double>; tmpl.cpp saw a definition and instantiated nothing, because nothing in tmpl.cpp used the template.
So the symbols the caller needs were never generated, and the linker says so.
A template definition in a
.cppfile produces a link error, not a compile error — and the error names a symbol rather than a mistake, which is why it is confusing the first time.
3. Repair One: Explicit Instantiation
Chapter 18.6 §8's construct, used for exactly this:
#include "tmpl.h"
template <typename T> T twice(T v) { return v + v; }
template int twice<int>(int); // generate it now
template double twice<double>(double);B. the same, plus EXPLICIT INSTANTIATION of the two types used:
link exit: 0
twice(21)=42 twice(1.5)=3.0It links. The definitions now exist in tmpl.cpp's object file because you asked for them by name.
And the limitation is immediate:
C. and if a THIRD type is used with explicit instantiation only for two:
link exit: 1
Undefined symbols for architecture arm64:Add a long caller and it breaks again. Every type must be listed, which means the set of usable types is fixed by the implementation file rather than by the callers — the opposite of what a template is for.
So this repair is right in exactly one situation: a template you deliberately support for a small, closed set of types, where you want to prevent other instantiations. Chapter 18.5 §10's closed-set condition applies, and the benefit is build time — the bodies are compiled once rather than in every translation unit.
4. Repair Two: Put the Definition in the Header
template <typename T> T twice(T v) { return v + v; } // definition IN THE HEADERD. definition in the HEADER (the normal answer):
link exit: 0
twice(21)=42 twice(1.5)=3.0This is the normal answer and it is what the entire standard library does: <vector>, <algorithm> and <memory> are headers containing definitions, which is why they are large and why including them costs compile time.
Any type works, with no list to maintain, because every translation unit that uses the template has what it needs to instantiate it.
5. Is That an ODR Violation?
The obvious worry: Chapter 6.8 established the one-definition rule, and a header included in two translation units puts the definition in both.
E. is a template definition in two TUs an ODR violation?
link exit: 0
twice(21)=42 other()=10 (same template, two TUs)It links and runs. Templates are explicitly exempt: a template definition may appear in multiple translation units provided the definitions are identical, and the linker merges the resulting instantiations into one. Chapter 18.6 §6 noted the merging; this is the rule that permits it.
That exemption is what makes §4 possible at all, and it is the same mechanism behind inline functions in headers.
6. Where to Put What
| Thing | Where |
|---|---|
| a function template's definition | the header |
| a class template's member definitions | the header (Chapter 18.4 §8) |
| a static data member of a class template | the header |
| an explicit instantiation | a .cpp, for a closed type set (§3) |
| a full specialisation of a function template | a .cpp is fine — it is an ordinary function (Chapter 18.8) |
Three conventions worth knowing because you will see them.
A .tpp, .ipp or -inl.h file, included at the bottom of the header, holds the definitions so the header reads as an interface. It is a readability device; the definitions are still in the header from the compiler's point of view.
inline is unnecessary on a function template — the ODR exemption in §5 already applies. It is required on a full specialisation, which is an ordinary function.
And the cost is compile time. Every translation unit including the header parses and instantiates, which Chapter 18.6 §6 identified as the per-instantiation compile cost. Module 24 is where build architecture is the subject; §3's explicit instantiation is the lever when it matters.
7. The Long Error, Measured
Now the second half of the chapter. A type with no operator<, handed to std::sort:
struct NoLess { int v; };
std::vector<NoLess> v{{3},{1},{2}};
std::sort(v.begin(), v.end());total diagnostic lines: 370
error lines: 20
'in instantiation of' notes: 64370 lines, 20 errors, 64 notes — from one mistake on one line.
And the first error is not in your code:
/…/c++/v1/__algorithm/comp.h:41:18: error: invalid oper…That is inside libc++. A reader scrolling from the top sees twenty errors in four or five standard-library headers and no obvious connection to anything they wrote.
8. How to Read It
The method, and it works every time:
Find the last
in instantiation of … requested herenote that names a file of yours. That is the line you got wrong.
badtype.cpp:6:10: note: in instantiation of function template specialization
'std::sort<std::__wrap_iter<NoLess *>>' requested hereLine 6 — the std::sort call. And the names in the note say what was being instantiated when things failed, which is often enough on its own: std::sort over NoLess* iterators.
Then read the first error: line for what was wrong — here invalid operands to binary expression inside the comparison helper, which is < failing.
So the two lines that matter, out of 370:
| Read | To learn |
|---|---|
the first error: | what could not be done — the missing operation |
the last in instantiation of naming your file | which of your lines caused it |
Everything between them is the chain of instantiations connecting the two — Chapter 18.6 §5's note, repeated once per layer. The length is the depth of the call chain, not extra information.
Measured, only two lines of my own file appear anywhere in the 370: the include on line 1 and the call on line 6.
9. Why It Is Long, and the Fix
The comparison that explains it. Chapter 18.2 §8 measured a simple template failing on the same kind of mistake:
noless.cpp (a one-line template, missing operator<):
diagnostic lines: 7 errors: 1Seven lines against three hundred and seventy, for the same category of error. The difference is not templates — it is that std::sort instantiates a comparison object, which instantiates a comparison helper, inside an introsort, inside an insertion sort, four layers down. Each layer adds a note.
Template diagnostics are long in proportion to instantiation depth, not in proportion to how template-y the code is. A template you wrote, failing on a type you chose, is usually short.
And the fix, which Chapter 18.10 §5 measures properly: state the requirement up front with a static_assert, and the same failure becomes
total diagnostic lines: 7 errors: 1
error: static assertion failed due to requirement 'has_less<NoLess, void>::value':
mySort requires T to support a < b370 lines down to 7, with a message you wrote. That is the single highest-leverage habit in template-heavy code, and C++20's concepts make it the default rather than a technique.
10. A Practical Routine
When a template diagnostic appears:
1. Do not scroll from the top. The top is inside somebody else's header.
2. Search for your own filename in the diagnostic. The last in instantiation of note naming it is your line.
3. Read the first error: line for the missing operation.
4. Check that operation on your type — usually a missing operator<, operator==, a missing copy constructor, or a missing default constructor.
5. If this is a template you own, add a static_assert so the next person gets seven lines.
With compiler help: clang's -ferror-limit=1 cuts the noise to the first error, and -fno-diagnostics-show-template-tree / -fdiagnostics-show-template-tree trade a different presentation. Neither is a substitute for step 2.
11. What to Actually Do
Put template definitions in headers — bodies included.
Use a .tpp or -inl.h include at the bottom of the header if you want the header to read as an interface.
Do not add inline to a function template — the ODR exemption already covers it.
Use explicit instantiation only for a deliberately closed type set, and expect to maintain the list.
Do not fear a template definition in two translation units — templates are ODR-exempt and the linker merges.
Search a long diagnostic for your own filename rather than reading from the top.
Read the first error: for what and the last instantiation note for where.
And put a static_assert in every template you own — measured, 370 lines down to 7.
12. Mistakes Worth Making Once
A template definition in a .cpp. Measured: a link error, naming a symbol rather than the mistake.
Reading that link error as a build-system problem. It is a placement problem.
Explicit instantiation as the default fix. Measured: add a third type and it breaks again.
Fearing an ODR violation from a header definition. Measured: two TUs linked and ran.
Adding inline to a function template. Unnecessary — though it is required on a full specialisation.
Scrolling a 370-line diagnostic from the top. The top is inside libc++.
Concluding template errors are unreadable. Measured: 7 lines for a simple template; the length is depth.
Shipping a template with no static_assert on its requirements.
Forgetting that header-only templates cost compile time in every including translation unit.
13. Interview Relevance
"Why do template definitions go in headers?" Because instantiation needs the definition and happens in the translation unit that supplies the type. The strong answer derives it rather than citing it as a convention.
"What happens if you put one in a .cpp?" A link error — measured, undefined symbols, because nothing in that file instantiated the template.
"How can you make that work anyway?" Explicit instantiation for each type used — measured working, and measured breaking when a third type appears.
"Is a header definition an ODR violation?" No — templates are exempt and the linker merges identical instantiations. Measured across two TUs.
"Do function templates need inline?" No. A full specialisation does.
"How do you read a 370-line template error?" Find the last in instantiation of note naming your file for where, and the first error: line for what. Two lines out of 370.
"Why are they so long?" Instantiation depth — one note per layer. Measured: 7 lines for a one-level template, 370 through std::sort.
"How would you make them shorter?" A static_assert on the requirement — measured, 370 to 7.
14. What You Should Be Able to Say Now
- A template's definition must be visible wherever it is instantiated, which follows from Chapter 18.6 §1.
- So definitions go in headers, bodies included.
- Measured: a definition in a
.cppgaveUndefined symbols for architecture arm64— a link error, not a compile error. - Both translation units were individually valid — the caller emitted calls, the definition file instantiated nothing.
- Measured: explicit instantiation of the used types made it link and run.
- Measured: adding a third type broke it again — the type set becomes fixed by the implementation file.
- So explicit instantiation suits a deliberately closed type set, and buys build time.
- Measured: the definition in the header links and runs for any type — what the standard library does.
- Measured: the same template in two translation units linked cleanly — templates are ODR-exempt and instantiations are merged.
inlineis unnecessary on a function template and required on a full specialisation.- Measured: a
std::sortfailure produced 370 lines, 20 errors, 64 instantiation notes, with the first error inside libc++. - Only two lines of my own file appeared in all 370.
- The method: last
in instantiation ofnote naming your file for where, firsterror:line for what. - Measured: a simple template's failure was 7 lines — the length is instantiation depth, not templates.
- Measured: a
static_assertreduced the same failure from 370 lines to 7, with your own message. - Header-only templates cost compile time in every including translation unit.
15. Exercises
Exercise 1 — reproduce it. Put a function template's definition in a .cpp, call it from another, and report the exact failure and at which stage.
Exercise 2 — explain it. Say why both translation units are individually valid.
Exercise 3 — two repairs. Apply each, then add a caller using a third type and say which repair survives.
Exercise 4 — the ODR question. Say whether a header definition in two TUs is a violation, and what rule permits it.
Exercise 5 — placement table. For a function template, a class template's members, a static data member, and a full specialisation, say where each goes.
Exercise 6 — read one. Take a long template diagnostic and name the two lines you would read, in order.
Exercise 7 — the length. Explain why a one-level template's failure is short and a std::sort failure is not.
Exercise 8 — shorten it. Add a static_assert to a template of yours and report the before and after line counts.
16. Summary
- Template definitions go in headers, because instantiation needs the definition where the type is supplied.
- Measured: a definition in a
.cppgave a link error — undefined symbols, with both TUs individually valid. - Measured: explicit instantiation repairs it for the listed types and breaks on the next one — so it suits a closed type set and buys build time.
- Measured: a header definition in two translation units links cleanly — templates are ODR-exempt and instantiations merge.
inlineis unnecessary on a template and required on a full specialisation.- Measured: a
std::sortfailure was 370 lines, 20 errors, 64 notes, first error inside libc++, with only two lines of my file named. - The method: the last
in instantiation ofnote naming your file is where; the firsterror:line is what. - Measured: a simple template failed in 7 lines — length is depth, not templates.
- Measured: a
static_asserttook the same failure from 370 lines to 7.
Templates can now be written, instantiated, placed and debugged. What they cannot yet do is behave differently for one particular type — and that need is real rather than hypothetical, because Chapter 18.2 §9 already met it: smaller compares const char* arguments as addresses rather than as text, which is almost never what the caller meant. That chapter's answer was an ordinary overload, and it worked. The language also offers a dedicated construct for the job, in two forms — one that replaces the template for a single exact type and one that replaces it for a whole family, such as any pointer or any pair whose halves are the same. The next chapter measures all three approaches, and the reason the overload came first is worth stating in advance: full specialisation of a function template interacts with overload resolution in a way that surprises people, and this chapter's measurement of it reaches a function nobody expected.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
