xSKILLby Infirals
Register

C++ for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Function and class templates

Inheritance, operators, and templates13 min readLesson 5 of 5

5.1 Code written once for many types

A template describes a function or class in terms of one or more type parameters. The compiler generates a concrete version for each set of types the program uses. larger(3, 7) creates a version for int, and larger<std::string>("Ada", "Mei") creates one for std::string.

The generated code is as type-safe as hand-written code. The template body must compile for each type it receives, so a type that lacks > produces an error at the point of instantiation. That error can be long, and the useful part usually names the operation that failed.

cpp
#include <iostream>
#include <string>

template <typename T>
T larger(T a, T b) {
    return a > b ? a : b;
}
int main() {
    std::cout << "Int: " << larger(3, 7) << "\n";
    std::cout << "Text: " << larger<std::string>("Ada", "Mei") << "\n";
    return 0;
}

Output

Int: 7
Text: Mei

5.1.1 Deduction and explicit arguments

The compiler deduces T from the function arguments. Both arguments must agree on the same T for a one-parameter template. larger(3, 4.5) fails because one argument suggests int and the other suggests double.

A call can name the type explicitly: larger<double>(3, 4.5). Both arguments then convert to double. A template with two type parameters avoids the conversion, and the return type can be written with auto so the compiler chooses a type that fits the operation.

cpp
#include <iostream>

template <typename A, typename B>
auto add(A a, B b) {
    return a + b;
}
int main() {
    std::cout << "Sum: " << add(2, 3.5) << "\n";
    return 0;
}

Output

Sum: 5.5

5.1.2 Class templates

A class template puts the type parameter on the class: template <typename T> class Box. Each instantiation such as Box<int> or Box<std::string> is a distinct type with its own member functions.

Member functions defined outside the class need the template header and the class name with its parameter: template <typename T> T Box<T>::get() const. Many templates keep their definitions inside the class or in the same header, because the compiler needs the body when it instantiates the type.

cpp
#include <iostream>
#include <string>

template <typename T>
class Box {
public:
    explicit Box(T value) : value_(value) {}
    T get() const { return value_; }
private:
    T value_;
};
int main() {
    Box<int> number(42);
    Box<std::string> text("notes");
    std::cout << number.get() << " " << text.get() << "\n";
    return 0;
}

Output

42 notes

5.1.3 Templates with containers

A function template can take a container by const reference and work with any element type. T sum(const std::vector<T>&) accepts a vector of integers or a vector of doubles, and T total{} creates a zero or empty starting value for the element type.

The requirements on T are implicit in the body: += must work, and T{} must be valid. Document those requirements in a comment when they are not obvious. C++20 concepts can state them in the signature, while C++17 relies on the body and on readable error messages.

cpp
#include <iostream>
#include <vector>

template <typename T>
T sum(const std::vector<T>& values) {
    T total{};
    for (const T& value : values) {
        total += value;
    }
    return total;
}
int main() {
    std::vector<int> counts = {2, 4, 6};
    std::cout << "Total: " << sum(counts) << "\n";
    return 0;
}

Output

Total: 12

5.1.4 Where template code lives

A template definition usually belongs in a header. The compiler needs the full body to generate a version for a new type, and other translation units include the header to do that themselves. Putting the definition in a .cpp file that callers cannot see produces unresolved external symbols at link time.

This differs from ordinary functions, where a declaration is enough at the call site. The cost is longer compile times and more code in each translation unit. Template errors often show a chain of instantiations, and the first line to read is the one that names the missing operation.

cpp
#include <iostream>
#include <string>
#include <vector>

template <typename T>
void print_all(const std::vector<T>& values) {
    for (const T& value : values) {
        std::cout << value << "\n";
    }
}
int main() {
    print_all(std::vector<int>{1, 2, 3});
    print_all(std::vector<std::string>{"a", "b"});
    return 0;
}

Output

1
2
3
a
b

5.1.5 Limits and pitfalls

Templates increase compile time and binary size when many types are instantiated. A function template cannot be virtual, because the compiler needs the concrete type before the program runs. Template code also needs its requirements documented, since the signature does not always show them.

Use a template when the algorithm is the same for several types and the types are known at compile time. Use a base class with virtual functions when the set of types must grow at runtime, and an ordinary function when only one type is involved.

5.1.6 Practice

  1. Write a template smaller and call it with two integers and two strings.
  2. Add a second type parameter to larger and return a common result with auto.
  3. Write a class template Pair that stores two values of different types.
  4. Write sum for a vector of doubles and check the result.
  5. Move a template definition into a header and explain why the caller can then use it.

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01Where should a template's definition live?

    Usually in a header, because the compiler needs the full definition to instantiate it for each type. A definition in a separate .cpp file is not visible when another translation unit uses the template.

  • 02Why does `larger(3, 4.5)` fail to compile with one type parameter?

    Deduction finds int for T from the first argument and double from the second, so the two deductions disagree. The call needs one explicit type or a template with two parameters.

  • 03What is instantiation?

    The compiler generates a concrete function or class from the template for a specific set of template arguments. Each distinct set of arguments produces its own instantiation.