Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
tmpl.h
    template <typename T> T twice(T v);        // declaration only
Azvya Education Pvt. Ltd.VLSI Mentor
tmpl.cpp
    #include "tmpl.h"
    template <typename T> T twice(T v) { return v + v; }    // definition here
Azvya Education Pvt. Ltd.VLSI Mentor
main.cpp
    #include "tmpl.h"
    int main() { twice(21); twice(1.5); }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 1

It 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 .cpp file 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:

Azvya Education Pvt. Ltd.VLSI Mentor
tmpl.cpp
    #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);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
B. the same, plus EXPLICIT INSTANTIATION of the two types used:
   link exit: 0
  twice(21)=42  twice(1.5)=3.0

It links. The definitions now exist in tmpl.cpp's object file because you asked for them by name.

And the limitation is immediate:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Azvya Education Pvt. Ltd.VLSI Mentor
hdronly.h
    template <typename T> T twice(T v) { return v + v; }    // definition IN THE HEADER
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
D. definition in the HEADER (the normal answer):
   link exit: 0
  twice(21)=42  twice(1.5)=3.0

This 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

ThingWhere
a function template's definitionthe header
a class template's member definitionsthe header (Chapter 18.4 §8)
a static data member of a class templatethe header
an explicit instantiationa .cpp, for a closed type set (§3)
a full specialisation of a function templatea .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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct NoLess { int v; };
    std::vector<NoLess> v{{3},{1},{2}};
    std::sort(v.begin(), v.end());
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
total diagnostic lines: 370
error lines:            20
'in instantiation of' notes: 64

370 lines, 20 errors, 64 notes — from one mistake on one line.

And the first error is not in your code:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
/…/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 here note that names a file of yours. That is the line you got wrong.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
badtype.cpp:6:10: note: in instantiation of function template specialization
                  'std::sort<std::__wrap_iter<NoLess *>>' requested here

Line 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:

ReadTo learn
the first error:what could not be done — the missing operation
the last in instantiation of naming your filewhich 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
noless.cpp (a one-line template, missing operator<):
  diagnostic lines: 7   errors: 1

Seven 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  total diagnostic lines: 7   errors: 1
  error: static assertion failed due to requirement 'has_less<NoLess, void>::value':
         mySort requires T to support a < b

370 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

  1. A template's definition must be visible wherever it is instantiated, which follows from Chapter 18.6 §1.
  2. So definitions go in headers, bodies included.
  3. Measured: a definition in a .cpp gave Undefined symbols for architecture arm64 — a link error, not a compile error.
  4. Both translation units were individually valid — the caller emitted calls, the definition file instantiated nothing.
  5. Measured: explicit instantiation of the used types made it link and run.
  6. Measured: adding a third type broke it again — the type set becomes fixed by the implementation file.
  7. So explicit instantiation suits a deliberately closed type set, and buys build time.
  8. Measured: the definition in the header links and runs for any type — what the standard library does.
  9. Measured: the same template in two translation units linked cleanly — templates are ODR-exempt and instantiations are merged.
  10. inline is unnecessary on a function template and required on a full specialisation.
  11. Measured: a std::sort failure produced 370 lines, 20 errors, 64 instantiation notes, with the first error inside libc++.
  12. Only two lines of my own file appeared in all 370.
  13. The method: last in instantiation of note naming your file for where, first error: line for what.
  14. Measured: a simple template's failure was 7 lines — the length is instantiation depth, not templates.
  15. Measured: a static_assert reduced the same failure from 370 lines to 7, with your own message.
  16. 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 .cpp gave 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.
  • inline is unnecessary on a template and required on a full specialisation.
  • Measured: a std::sort failure 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 of note naming your file is where; the first error: line is what.
  • Measured: a simple template failed in 7 lines — length is depth, not templates.
  • Measured: a static_assert took 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.