Learn C++ STL

Lesson 5 of 9 ¡ From C to Just-Enough C++

Module 1 ¡ From C to Just-Enough C++

Templates, the Idea: One Function, Every Type

FreeReading

In this lesson

  • Read std::vector<int>, std::map<std::string, int> and std::pair<int, int> as "a template, filled in with types".
  • Call std::max, std::min and std::swap on any type, and say what the compiler wrote for each call.
  • Find the one line that matters in a long GCC 12 template error, and fix the cause.

In C, Amara needed the larger of two numbers three times last term: for marks, for prices and for distances. So she wrote max_int, max_double and max_ll, three functions with the same body. In C++ she writes std::max and never writes the body at all. The trick behind it is called a template, and every container and algorithm in this track is one.

Three max functions in C, one in C++

Here is the C way. The bodies are identical; only the types differ, and C needs a separate name for each.

#include <stdio.h>

int max_int(int a, int b)
{
    return a > b ? a : b;
}

double max_double(double a, double b)
{
    return a > b ? a : b;
}

long long max_ll(long long a, long long b)
{
    return a > b ? a : b;
}

int main(void)
{
    printf("%d\n", max_int(3, 7));
    printf("%.2f\n", max_double(2.5, 1.25));
    printf("%lld\n", max_ll(4000000000LL, 3000000000LL));
    return 0;
}
7
2.50
4000000000

And here is C++, with one name for all of them, strings included.

#include <algorithm>
#include <iostream>
#include <string>

int main()
{
    std::cout << std::max(3, 7) << '\n';
    std::cout << std::max(2.5, 1.25) << '\n';
    std::cout << std::max(4000000000LL, 3000000000LL) << '\n';
    std::cout << std::max(std::string("pear"), std::string("apple")) << '\n';
    return 0;
}
7
2.5
4000000000
pear

std::max lives in the header <algorithm>. For two strings it returns the one a dictionary lists later, so "pear" beats "apple". So one name, std::max, does the work of three C functions and a fourth C never had.

A template is a recipe with a blank for the type

A template is code written once with a blank where a type goes. The standard library wrote max as "compare two values of some type T with <, return the larger". When you call it on two int values, T is filled in with int.

For a function template the compiler usually works out T from the arguments, as above. For a container you name the type yourself, in angle brackets. Read <...> as "filled in with".

Reading a template's name

std::vector<int>                  a vector, filled in with int
std::vector<std::string>          a vector, filled in with string
std::map<std::string, int>        a map, filled in with string keys and int values
std::pair<std::string, int>       a pair, filled in with a string and an int
std::max(3, 7)                    max, with T worked out as int
  • The name before the brackets is the template: vector, map, pair, max.
  • Inside the brackets are the types it is filled in with, separated by commas.
  • For a function template such as max, the brackets are usually left out and the types come from the arguments.

So std::vector<long long>, which you met in lesson 04, is one recipe, "vector", filled in with one type, long long.

What the compiler does with a template

A template is not code the machine can run. It is a pattern the compiler copies out, with the blank filled in, the first time a program asks for that type. The filled-in copy is called an instantiation. Ask for max on int, double and std::string, and the compiler writes three ordinary functions.

One template, three instantiations template: T max(T a, T b) compare with <, return the larger the compiler fills in T max<int> std::max(3, 7) max<double> std::max(2.5, 1.25) max<std::string> std::max("pear", "apple") one recipe written once, three functions compiled

This is not a figure of speech. Compile a program that calls std::max on those three types on Compiler Explorer with GCC 12, optimisation off (-O0). The assembly listing names three separate functions: std::max<int>(int const&, int const&), std::max<double>(double const&, double const&) and one for std::string. At the Playground's -O2 they disappear: each one is so small that GCC 12 copies its body straight into main.

A class template works the same way. std::vector<int> and std::vector<std::string> are two different types the compiler wrote from one recipe. They do not mix: assigning one to the other gets error: no match for 'operator=' (operand types are 'std::vector<int>' and 'std::vector<std::__cxx11::basic_string<char> >') from GCC 12. The odd std::__cxx11::basic_string<char> is the real name of std::string in GCC's library, and you will meet it in many messages.

One honest line about scope. This track reads and uses templates: every container, every algorithm. Writing your own template, with the word template and a T of your own, belongs to the C++ track. So in this track the compiler writes the instantiations, and your job is to fill in the types and read its messages.

std::max, std::min and std::swap on any type

Three small templates turn up in almost every program. std::max and std::min return the larger and the smaller of two values. std::swap exchanges two variables, through references, exactly like Maria's swapRef in lesson 03.

#include <algorithm>
#include <iostream>
#include <string>

int main()
{
    int a = 4;
    int b = 9;
    std::cout << "max " << std::max(a, b) << ", min " << std::min(a, b) << '\n';
    std::cout << "max of three " << std::max({a, b, 2}) << '\n';

    std::swap(a, b);
    std::cout << "after swap: a = " << a << ", b = " << b << '\n';

    std::string first = "Kenji";
    std::string second = "Amara";
    std::swap(first, second);
    std::cout << first << ' ' << second << '\n';
    return 0;
}
max 9, min 4
max of three 9
after swap: a = 9, b = 4
Amara Kenji

Each of the three has one rule. std::max and std::min need two values of the same type that can be compared with <. For more than two, put them in braces: std::max({a, b, 2}) takes a whole list. std::swap needs two variables of the same type, never a plain number, because it writes through references.

So one template call works on int, double, std::string and every other type that has a <, and the compiler writes the version you need.

Reading a template error: the one line that matters

A mistake inside a template call can make GCC 12 print dozens of lines for one error. They are not dozens of problems. The compiler is listing every version of the function it tried and why each failed. One or two lines tell you what is wrong, and the rest is the search.

The rule for finding them has three steps.

  1. Find the first line that names your file and says error:. Files inside the compiler's own folders (paths ending in bits/stl_algobase.h and the like) are the library, not you.
  2. If the first error: is inside a library file, look just above it for required from here. The line with that phrase names your file and your line.
  3. Read the note: that gives a reason in plain words: "deduced conflicting types", "no known conversion", "no match for 'operator<'". Skip the notes that only list candidates.

Here are three real errors, each from GCC 12 at the Playground's command line, -O2 -std=c++17. Compiler Explorer names your file <source>; the long paths into the compiler's own files are cut to their last part here.

Gallery 1: two types where one is needed. Kenji keeps a running total as long long and wants it never below zero.

long long total = 0;
long long best = std::max(total, 0);

GCC 12 prints over 30 lines. The two to read:

<source>:7:30: error: no matching function for call to 'max(long long int&, int)'
<source>:7:30: note:   deduced conflicting types for parameter 'const _Tp' ('long long int' and 'int')

The first names the call and the two types it got. The note says why: _Tp is the library's name for the blank, and it cannot be long long and int at once. The fix is to make both the same type: std::max(total, 0LL), where LL makes the zero a long long.

Gallery 2: a type with no <. Maria stores parcels as a C struct and asks for the heavier one.

struct Parcel {
    int weight;
    int price;
};

Parcel a = {3, 120};
Parcel b = {5, 80};
Parcel heavier = std::max(a, b);

GCC 12 prints over 40 lines, and the first error: is inside the library. Step 2 finds your line:

.../bits/stl_algobase.h: In instantiation of 'constexpr const _Tp& std::max(const _Tp&, const _Tp&) [with _Tp = Parcel]':
<source>:13:30:   required from here
.../bits/stl_algobase.h:259:15: error: no match for 'operator<' (operand types are 'const Parcel' and 'const Parcel')

[with _Tp = Parcel] says which instantiation broke. required from here points at line 13, your call. The error says the reason: std::max compares with <, and nobody said what "less" means for a parcel. Heavier by weight? Cheaper by price? The compiler cannot guess. Compare the field you mean, std::max(a.weight, b.weight); Module 12 shows how to hand an algorithm your own rule.

Gallery 3: the wrong type into a container. Bob puts a name into a list of marks.

std::vector<int> marks = {70, 85};
std::string name = "Zara";
marks.push_back(name);

GCC 12 prints about 20 lines. The two to read:

<source>:9:20: error: no matching function for call to 'std::vector<int>::push_back(std::string&)'
.../bits/stl_vector.h:1276:35: note:   no known conversion for argument 1 from 'std::string' {aka 'std::__cxx11::basic_string<char>'} to 'const std::vector<int>::value_type&' {aka 'const int&'}

push_back adds one element at the end (Module 2). The note says it wanted a const int&, because this vector was filled in with int, and got a std::string. The {aka ...} parts translate the library's names into plain ones. The fix depends on what Bob meant: a second vector for names, or a pair of name and mark (lesson 06).

So a 50-line template error is one mistake and a long search. Your file's first error:, or the required from here above the library's, plus one reason note, is all you need to read.

Example 1: the higher and the lower of two scores

Alice reads two game scores and wants them labelled. One call each.

#include <algorithm>
#include <iostream>

int main()
{
    int a = 0;
    int b = 0;
    std::cin >> a >> b;

    std::cout << "higher " << std::max(a, b) << ", lower " << std::min(a, b) << '\n';
    return 0;
}
higher 85, lower 72

That output is for the input 72 85. Both arguments are int, so the compiler made max and min for int. Zara tries 85 85: both lines say 85, which is correct.

Run in Compiler
Example 2: Zara's clamp, one line long

Lesson 03 kept a reading inside -40 to 60 with two if statements. std::max pulls a value up to the floor, and std::min pulls it down to the ceiling, so one line does both.

#include <algorithm>
#include <iostream>

int main()
{
    int readings[3] = {25, -55, 70};

    for (int& t : readings) {
        t = std::min(std::max(t, -40), 60);
    }
    std::cout << readings[0] << ' ' << readings[1] << ' ' << readings[2] << '\n';
    return 0;
}
25 -40 60

Read it from the inside: std::max(t, -40) lifts -55 to -40, then std::min(..., 60) lowers 70 to 60. The output matches lesson 03's to the character. The range-for uses int& because it writes into the array (lesson 04).

Run in Compiler
Example 3: Amara's shelf, from both ends

Amara reads n books, each a one-word title and a price. She wants the cheapest and dearest price, and the first and last title in dictionary order. Two templates do it, each on two types.

#include <algorithm>
#include <iostream>
#include <string>

int main()
{
    int n = 0;
    std::cin >> n;

    std::string title;
    int price = 0;
    std::cin >> title >> price;

    std::string firstTitle = title;
    std::string lastTitle = title;
    int cheapest = price;
    int dearest = price;

    for (int i = 1; i < n; i++) {
        std::cin >> title >> price;
        firstTitle = std::min(firstTitle, title);
        lastTitle = std::max(lastTitle, title);
        cheapest = std::min(cheapest, price);
        dearest = std::max(dearest, price);
    }
    std::cout << "prices " << cheapest << " to " << dearest << '\n';
    std::cout << "titles " << firstTitle << " to " << lastTitle << '\n';
    return 0;
}
prices 120 to 450
titles Dracula to Ulysses

That output is for the input 4, then Emma 300, Ulysses 450, Dracula 120 and Matilda 200, one book per line. Everything starts from the first book, a real value, so no guess about "a very large price" is needed. std::min on two strings keeps the one a dictionary lists first. Capital letters sort before small ones in that order, so mixing them would surprise you; every title here starts with a capital.

Run in Compiler

Where this is used

  • The whole STL. Every container in this track is a class template (std::vector<T>, std::map<K, V>). Every algorithm is a function template, so one std::sort sorts numbers, strings and pairs alike (Module 12).
  • GCC's own library. The messages above quote it: libstdc++ declares max in bits/stl_algobase.h as a template with one type parameter, _Tp, which is why the errors talk about _Tp.
  • Eigen. This C++ library for matrices is built from templates. Eigen::Matrix<float, 3, 3> fills in the number type and even the sizes, so the compiler can write code for exactly a 3 by 3 matrix of float.
  • Qt. The C++ framework behind many desktop apps has its own container templates, such as QList<T> and QMap<Key, T>, read exactly the way this lesson reads std::vector<int>.

Common mistakes

1. Three values to std::max without braces.

std::cout << std::max(a, b, c) << '\n';

An error at every command line, reported inside the library: error: '__comp' cannot be used as a function. The second form of std::max takes a third argument, a comparison rule (Module 12), so c was taken as a rule and could not be called. Write std::max({a, b, c}). You will try it because a function that takes two values should surely take three.

2. A class template with no type.

std::vector v;

An error at every command line: error: class template argument deduction failed:, then over 40 lines of candidates. The useful note is couldn't deduce template parameter '_Tp': with no values in it, nothing says what the vector holds. Write std::vector<int> v;. You will leave it out because auto taught you the compiler can work types out; it can, but only from a value.

3. Mixing int and long long in std::min or std::max.

long long smallest = 5000000000LL;
smallest = std::min(smallest, 1000000000);

An error at every command line: error: no matching function for call to 'min(long long int&, int)', with the note on conflicting types, as in gallery 1. Write the literal with LL: 1000000000LL. You will make this one in every problem whose sums need long long, because the plain number looks harmless.

4. Swapping a value that is not a variable.

std::swap(a, 5);

An error at every command line: error: no matching function for call to 'swap(int&, int)', and a few lines later lesson 03's own message, error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'. std::swap writes into both arguments through references, and 5 has no box to write into, the rule from lesson 03. Put the value in a variable first.

Brain teaser

Bob wants the larger of 3 and 4.5.

#include <algorithm>
#include <iostream>

int main()
{
    double best = 4.5;
    best = std::max(best, 3.0);
    std::cout << best << '\n';
    return 0;
}

That version compiles and prints 4.5. His first version was the one-liner std::cout << std::max(3, 4.5) << '\n';, and GCC 12 refused it. Why does std::max(3, 4.5) not compile? There are two different fixes that keep both numbers exactly as Bob typed them except for one change each; find both.

One fix changes how one of the numbers is written. The other leaves both numbers alone and tells the template what to fill its blank with, the way std::vector<int> does.

Exercise 1Easy

Kenji times three practice laps and wants the fastest and the slowest, using std::min and std::max rather than if statements.

Input. Three decimal numbers, the lap times in seconds.

Output. Two lines: fastest and the smallest time, then slowest and the largest, each printed with plain std::cout <<.

Constraints. Each time is between 1 and 1000, with at most two digits after the point.

Sample. Input 62.5 58.25 61 gives fastest 58.25 and slowest 62.5 on two lines.

#include <algorithm>
#include <iostream>

int main()
{
    double a = 0;
    double b = 0;
    double c = 0;
    std::cin >> a >> b >> c;

    // Use std::min and std::max to find the fastest and the slowest lap.

    return 0;
}

Not graded on its own. There are two ways to give three values to std::min, and this lesson shows both; try each.

Run in Compiler
Exercise 2Easy

David typed his first and last name the wrong way round. Swap the two strings twice: first the long way, with a third variable, then the short way, with std::swap, and print them after each swap.

Input. Two words.

Output. Two lines: the two words after the long swap, then after the short swap, separated by one space.

Constraints. Each word is 1 to 20 letters.

Sample. Input Smith David gives David Smith, then Smith David.

#include <iostream>
#include <string>
#include <utility>

int main()
{
    std::string first;
    std::string second;
    std::cin >> first >> second;

    // The long way: swap first and second with a third string, then print.

    // The short way: swap them back with std::swap, then print again.

    return 0;
}

Not graded on its own. std::swap is declared in <utility>, which is why the starter includes it; lesson 07 lists which header holds what.

Run in Compiler
Exercise 3Medium

Kenji's sensor station sends three kinds of reading: whole-number counts, decimal temperatures and the names of the stations that answered. Each line of the log is one batch of one kind, and the dashboard shows the largest value of each batch. For a word, "largest" means the one a dictionary lists last.

Input. The first line holds t. Each of the next t lines starts with a type, int, double or string, then k, then k values of that type.

Output. t lines, each the largest value of its batch, printed with plain std::cout <<: a double 2.50 prints as 2.5, and 3.00 as 3.

Constraints. 1 <= t <= 800 and 1 <= k <= 100. An int value is between -1000000000 and 1000000000. A double is between -1000 and 1000, with at most two digits after the point. A string is 1 to 10 lowercase letters.

Sample. Input 3, then int 3 4 -2 7, double 2 2.5 1.25 and string 3 pear apple zebra, gives 7, 2.5 and zebra on three lines.

#include <algorithm>
#include <iostream>
#include <string>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int t = 0;
    std::cin >> t;
    for (int i = 0; i < t; i++) {
        std::string type;
        int k = 0;
        std::cin >> type >> k;

        // type is "int", "double" or "string". Read the k values into
        // variables of that type, keep the largest with std::max,
        // and print it on its own line.
    }
    return 0;
}

Graded as max-of-each-line. The hidden tests include batches of one value and batches whose values are all negative. They also have doubles such as 3.00 and -0.5, and strings where one word starts another (app and apple).

Run in Compiler

Common doubts

  • Does a template make my program slower?

    No. An instantiation is an ordinary function, compiled like any other. At the Playground's -O2, GCC 12 copied the three small max functions straight into main, so there was not even a call left.

  • Is a template the same as a C macro?

    Both write code for you, but a macro is text pasted in before compiling, with no idea of types. A template is checked by the compiler, type by type, which is exactly why it can give the messages in the gallery. A macro would have pasted the mistake in silently.

  • Why is the type called _Tp in the messages and T in this lesson?

    The name of the blank is the template author's choice. Books write T; GCC's library writes _Tp, because names starting with an underscore and a capital are reserved for the library and cannot clash with yours.

  • If std::max works on strings, how does it compare them?

    With the string's own <, character by character, by character code, like C's strcmp. So "Zara" comes before "apple", because capital letters have smaller codes than small ones in the ASCII table.

  • When will I write a template of my own?

    In the C++ track, which teaches the word template, type parameters and their rules. Everything in the STL track works without it, because the library already wrote the templates you need.

Key takeaways

  • A template is code written once with a blank for a type; <...> reads as "filled in with".
  • The compiler writes one instantiation per type a program asks for: max<int>, max<double>, and so on.
  • std::max, std::min and std::swap work on any type with a <, but both arguments must be the same type.
  • In a long template error, read your file's first error: (or the required from here line) and one reason note.
  • This track reads and uses templates; writing them is the C++ track's.

Next, lesson 06 fills in a template with two types at once: std::pair, and its bigger sibling std::tuple.

End of lesson 5

Mark it done, and your progress moves with you.

Next: pair and tuple: Two or More Things With One Name