Lesson 8 of 8
Lesson 11.5 taught reading a stack trace. This is the same skill for compile time, and it is the one that decides whether templates are usable for you or not.
The wall
// #include <iostream>
// #include <map>
// #include <string>
// #include <vector>
//
// struct Widget { int id; };
//
// int main() {
// std::map<std::string, std::vector<Widget>> registry;
// registry["a"].push_back(Widget{1});
// std::cout << registry << "\n"; // maps do not have operator<<
// return 0;
// }
GCC 13 produces 338 lines for that, containing 122 note: lines. Nobody reads all of it.
Nobody should.
Here is the first error, which is the entire diagnosis:
main.cpp: In function 'int main()':
main.cpp:11:15: error: no match for 'operator<<' (operand types are 'std::ostream'
{aka 'std::basic_ostream<char>'} and 'std::map<std::__cxx11::basic_string<char>,
std::vector<Widget> >')
11 | std::cout << registry << "\n";
| ~~~~~~~~~ ^~ ~~~~~~~~
| | |
| | std::map<std::basic_string<char>, std::vector<Widget> >
| std::ostream
Your file, your line, the operation, and both operand types. The remaining 330 lines are the
compiler listing every operator<< in the standard library and explaining why each one did not
match — useful to the compiler author, not to you.
The technique
- Read the first error only. Everything after it is usually a consequence.
- Find the line in your own file. The same rule as frame #0 in a stack trace: skip the library, find your name.
- Read the types, ignoring the noise. Mentally rewrite
std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>asstd::string. - Read the
required fromchain. It is the instantiation path, newest last. The bottom entry is your call. - Fix, recompile. One error at a time. Fixing the first often removes fifty.
Two substitutions make most standard library types readable:
| What you see | What it is |
|---|---|
| std::__cxx11::basic_string<char, ...> | std::string |
| std::basic_ostream<char, ...> | std::ostream |
| __gnu_cxx::__normal_iterator<T*, std::vector<T>> | std::vector<T>::iterator |
| std::_Rb_tree_iterator<...> | a map or set iterator |
Making the compiler help
g++ -std=c++20 -fno-diagnostics-show-caret main.cpp
On the 338-line example above:
| Flags | Lines |
|---|---|
| default | 338 |
| -fmax-errors=1 | 328 |
| -fno-diagnostics-show-caret | 137 |
-fmax-errors=1 barely helps here, because this is one error with 122 notes — worth knowing,
since the flag is the usual first suggestion. Suppressing the caret diagrams cuts the output by
more than half, because each note carries three lines of source art.
Other flags worth knowing:
-fconcepts-diagnostics-depth=2 # GCC: explain why a concept was not satisfied
-ftemplate-backtrace-limit=0 # show the full instantiation chain, not a summary
-fdiagnostics-color=always # colour, when piping through less -R
And a genuinely different option: Clang's template diagnostics are usually shorter and better organised than GCC's. If you have both, compiling a confusing error with the other compiler is often faster than deciphering the first one.
The four errors that produce most of the pain
1. A missing operation on T
error: no match for 'operator<' (operand types are 'const Widget' and 'const Widget')
Means: the template needs an operation your type does not provide.
Fix: give the type the operator, pass a comparator, or constrain the template with a concept so the error moves to the call (lesson 12.7).
The instantiation chain is what tells you which template wanted it. Read required from upward
until you reach your own call.
2. A template split across a header and a .cpp
This one is a linker error, which is why it confuses people — the code compiles fine:
/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x28): undefined reference to `Box<int>::Box(int)'
/usr/bin/ld: main.cpp:(.text+0x34): undefined reference to `Box<int>::get() const'
collect2: error: ld returned 1 exit status
Means: you declared the template in box.h and defined it in box.cpp. Compiling box.cpp
instantiated nothing, because nothing there uses Box<int>. Compiling main.cpp saw a
declaration with no body, so it emitted a call to a function that does not exist.
Fix: put the definitions in the header (lesson 12.1). If you truly need them in a .cpp, add
an explicit instantiation there:
// At the bottom of box.cpp — generate these versions here, and only these:
// template class Box<int>;
// template class Box<std::string>;
That works, and it keeps compile times down for a template used with a handful of known types. It
fails the moment someone wants Box<double>.
3. typename missing before a dependent type
// template <typename T>
// void process(const std::vector<T>& v) {
// std::vector<T>::const_iterator it = v.begin(); // needs typename
// }
error: need 'typename' before 'std::vector<T>::const_iterator' because
'std::vector<T>' is a dependent scope
error: expected ';' before 'it'
Means: while parsing the template, the compiler does not yet know what T is, so it cannot
know whether std::vector<T>::const_iterator is a type or a static member. It assumes not a
type, and the line stops parsing.
Fix: typename std::vector<T>::const_iterator it = v.begin();
Note the second error — expected ';' — is a consequence of the first, which is rule 1 of the
technique in action.
In practice you write auto it = v.begin(); and the question never comes up. But you will read
older code full of typename, and now you know why it is there.
C++20 removed the requirement in many contexts, though not this one — GCC 13 reports it
identically under -std=c++17 and -std=c++20.
4. A member of a dependent base class is invisible
// template <typename T>
// class Base {
// protected:
// T value = T{};
// };
//
// template <typename T>
// class Derived : public Base<T> {
// public:
// void show() { std::cout << value << "\n"; } // 'value' not found
// };
error: 'value' was not declared in this scope
13 | std::cout << value << "\n";
| ^~~~~
Means: this is the one that looks like a compiler bug and is not. Base<T> depends on T, and
a specialization of Base might not have a value at all — so the compiler refuses to look in the
base class when parsing the template.
Fix: name it explicitly, either way:
// std::cout << this->value << "\n"; // most common
// std::cout << Base<T>::value << "\n"; // also fine
// using Base<T>::value; // once, in the class body
If you have ever wondered why template-heavy code is full of this-> on members that are obviously
in scope, this is the reason.
Worked example
main.cpp: In instantiation of 'void print_sorted(std::vector<T>&) [with T = Widget]':
main.cpp:24:18: required from here
/usr/include/c++/13/bits/stl_algo.h:1819:14: required from
'constexpr void std::__insertion_sort(...)'
/usr/include/c++/13/bits/predefined_ops.h:45:23: error: no match for 'operator<'
(operand types are 'Widget' and 'Widget')
Work it in the technique's order:
- Line 1 — a template being instantiated with
T = Widget. Your function, your type. - Line 2 —
main.cpp:24is where you called it. That is the line to look at. - Line 3 — inside
std::sort. Library. Skip. - Line 4 —
Widgethas nooperator<.
Four lines, and the whole thing is: line 24 sorts Widgets, and Widget cannot be compared. Give
Widget an operator< — or, in C++20, auto operator<=>(const Widget&) const = default;, which
generates all six comparisons at once.
Prevention
The errors get shorter when you make them impossible to reach:
- Constrain with concepts (lesson 12.7) so the failure is at the call, not inside the body.
static_assertearly in the template body, with a message you wrote:
// template <typename T>
// void process(const T& value) {
// static_assert(std::is_arithmetic_v<T>, "process() requires a numeric type");
// ...
// }
The assert fires before anything else does, and prints your sentence rather than the compiler's.
- Test each instantiation. A template with no
Box<std::string>anywhere in the codebase has never been compiled forstd::string. Instantiate it in a test, and the error arrives while you are writing it instead of a month later.
Try it
Write a template that fails for a type lacking the operation it needs, and read the error. Then
add a static_assert with your own message and read it again. Then constrain it with a concept
and read it a third time. Count the lines each version produces.
Checkpoint
- Read the first error; the rest is usually consequence and library noise.
- Find the line in your own file — the same rule as frame #0 in a stack trace.
- Mentally substitute
std::stringforstd::__cxx11::basic_string<char, ...>. - The
required fromchain is the instantiation path; your call is the bottom entry. -fno-diagnostics-show-caretcut a 338-line error to 137;-fmax-errors=1barely helped.- Clang's template errors are often shorter than GCC's — try the other compiler.
- Undefined reference to a template member means it was defined in a
.cpp, not the header. need 'typename'means a dependent type; writetypenameor useauto.'value' was not declaredin a derived template means you needthis->value.- Prevent the wall: constrain with concepts,
static_assertearly, and test each instantiation.
Questions about this lesson
Log in to ask a question.