---
# System prepended metadata

title: Шаблоны
tags: [cpp, ' templates', ' teaching', ' mipt', ' 3sem']

---

---
title: Шаблоны
tags: cpp, mipt, 3sem, teaching, templates
---

# Пятое занятие
#### 2022-10-01
## Шаблоны

### Идея шаблонов

Иногда для требуется написать функцию/класс, который бы работал не только с одним типом данных, а мог бы выполнять те же действия для почти любых типов данных. В этом случае можно воспользоваться обобщенным программированием (как на первом семинаре) или использовать встроенные средства языка, а именно - шаблоны.

### Шаблонные функции

Первая же идея использования шаблонов возникает при написании функций. 

Например, функция максимума из двух операндов может выглядеть следующим образом.
```cpp=
int max(int a, int b) {
    return a > b ? a : b;
}
double max(double a, double b) {
    return a > b ? a : b;
}
```

Однако, неудобно для каждого нового типа снова писать новую версию функции. В этом случае как раз помогают шаблоны.

Синтаксис шаблонов:
```cpp=
template<typename T> // keyword for templates
T mymax(T a, T b) {
    return a > b ? a : b;
}

int main() {
    std::cout << mymax<int>(1, 2) << '\n';
    // class MyClass must have operator '<'
    std::cout << mymax<MyClass>(MyClass(5), MyClass(10)) << '\n';
}
```

**Замечание 1:** Ключевое слово ```template``` действует только на ближайший блок кода (функцию или класс)

**Замечание 2:** Данные функции будут созданы только при их вызове.

**Замечание 3:** После объявления шаблона может идти только объявление.

**Замечание 4:** Объявление шаблонов возможно только в глобальной видимости, внутри класса или ```namespace```. Внутри функции нельзя объявлять новые шаблоны.

**Примечание 1:** Вместо ```typename``` можно использовать ```class``` по историческим причинам

### Перегрузка шаблонных функций

При перегрузке функций с шаблонами и без можно руководствоваться двумя правилами:

1. Точное соответствие лучше чем приведение типа
2. Частное лучше общего

Пример:

```cpp=
template<typename T, typename U>
void f(T a, U b) {
    std::cout << 1 << '\n';
}

template<typename T>
void f(T a, T b) {
    std::cout << 2 << '\n';
}

void f(int a, int b) {
    std::cout << 3 << '\n';
}

int main() {
    f(1, 1.0f);    // to call first function
    f(1.0f, 1.0f); // to call second function
    f(1, 1);       // to call third function
    f<int>(0.0, 1); // call 2
    f<int>(1, 1.0f); // call 1
    f<int, double>(1, 1.0f); // call 1
    f<int, double>(1.0f, 1); // call 1
    f<>(1.0, 1); // to let compiler decide which version to use (only template functions)
    f(1.0, 1); // to let compiler decide which version to use (template and non-template variants)
}
```

Пример 2:
```cpp=
template<typename T>
void f(T a) {
    std::cout << 1 << '\n';
}


// if T already has & in type, no more & is added
template<typename T>
void f(T& a) {
    std::cout << 1 << '\n';
}

int main() {
    f(1); // 1
    int x = 0;
    f(x); // CE, ambiguous call
    f<int&>(x); // still CE
}
```

**Примечание 1:** Для функции с несколькими шаблонными параметрами одного типа оба типа должны быть одинаковы при вызове этой функции.

### Шаблонные параметры по умолчанию

Компилятор умеет сам выводить типы, которые надо подставить в шаблон. Но если их количество не совпадает с количеством при вызове, то это вызовет ошибку.

```cpp=
template<typename T, typename U>
void h(T x) {
    U y;
    std::cout << y << '\n';
}

int main() {
    h(0); // CE
    
}
```

Кроме того, можно указывать значения по умолчанию для шаблонных параметров.

```cpp=
template<typename T, typename U = int>
void h(T x) {
    U y;
    std::cout << y << '\n';
}

int main() {
    h(0); // 
    
}
```

Вполне возможная ситуация:

```cpp=
template<typename T>
class A {
public:    
    A() = default;
    
    // common tempalte consructor
    template<typename U>
    A(U& a) {
        std::cout << 1 << '\n';
    }
    // copy constructor
    A(const A<T>& other) {
        std::cout << 2 << '\n';
    }
};

int main() {
    A<int> s;
    A<int> ss = s; // 1  
}
```

Такое происходит, так как первая версия предпочтительнее в силу того, что туда можно подставить любой тип.



## Шаблонные классы

Кроме шаблонных функций можно создавать шаблонные классы.

Пример шаблонного класса:
```cpp=
template<typename T>
class MyClass {
    // some code
};

void foo() {
    MyClass<int> tmp; // create templated class
    // some code
}
```

Кроме того, можно внутри шаблонного класса можно объявить шаблонный внутренний класс или функцию.

Пример шаблонных классов:
```cpp=
template<typename T>
class A {
public:
    T a;
    
    template<typename U>
    void foo(U arg1, T arg2) {
        // some code
    }
    
    template<typename U> // template inner class
    class Inner {
        U b;    
    };
};

template<typename T, typename U>
class S {
    T a;
    U b;
};
```

Если внутри шаблонного класса объявляется шаблонный класс или метод, то для его определения необходимо дважды указать ключевое слово ```template```.

```cpp=
template<typename T>
class A {
private:
    T a;
public:    
    template<typename U>
    void foo(U arg1, T arg2); // declaration
    
    template<typename U> // template inner class
    class Inner {
        U b;    
    };
};

template<typename T>
template<typename U>
void A<T>::foo(U arg1, T arg2) {
    // some code
}
```

**Примечание 1:** Шаблон с несколькими аргументами не то же самое что шаблон внутри шаблона

**Примечание 2:** При вызове шаблонной функции из шаблонного класса необязательно указывать шаблонные параметры явно.

Пример:

```cpp=
template<typename T>
class A {
private:
    T a;
public:
    template<typename U>
    void foo(U arg1, T arg2); // declaration
    
};

template<typename T>
template<typename U>
void A<T>::foo(U arg1, T arg2) {
    // some code
}

int main() {
    A<int> a;
    a.foo(1.5f, 1); // OK, first arg is float
    a.foo(1, "abc"); // CE, can't cast "abc" to int
}
```

### Шаблонные переменные

С С++14 появилась возможность создавать шаблонные переменные

Пример шаблонной переменной:
```cpp=
template<typename T>
double pi = 3.14;

template<typename T>
T pi = 3.14;
```

### Шаблонные alias

С С++11 есть возможность определять шаблонные ```alias```.

Пример:
```cpp=
template<typename T>
using umap = std::unordered_map<T, T>;
```

## Специализация шаблонов

При написании шаблонов может возникнуть ситуация, когда надо написать некоторый обобщенный класс или функцию, но такую, чтобы для некоторых типов они работали чуть-чуть по-другому. В этом случае можно использовать частичную специализацию шаблонов.

**Примечание 1:** Для функций возможна только полная специализация, т.е. версия функции с меньшим количеством шаблонных параметров считается новой, а не специализацией существующей.

### Специализация шаблонных классов

Пример написания специализации шаблонного класса:
```cpp=
template<typename T>
class S {
    T x;
};

template<>
class S<int> {
    int x;    
};
```

**Примечание 1:** Объявлять специализации можно только после объявления общей версии шаблона

### Специализация функций

Шаблонные функции тоже можно специализировать

```cpp=
template<typename T>
void f(T x) {
    std::cout << 1 << '\n';
}

template<>
void f(int x) {
    std::cout << 2 << '\n';
}

int main() {
    f(0); // 2
}
```

Однако, при добавлении обычной версии без шаблона с параметром ```int``` будет выбрана именно эта версия.

```cpp=
template<typename T>
void f(T x) {
    std::cout << 1 << '\n';
}

template<>
void f(int x) {
    std::cout << 2 << '\n';
}

void f(int x) {
    std::cout << 3 << '\n';
}

int main() {
    f(0); // 3
}
```

Тут срабатывает следующее правило: при подборе версий для вызова сначала выбирается та, которая лучше подходит, затем, если эта выбранная функция шаблонная, может быть выбрана её специализация.

### Порядок важен*

При специализации версии специализаций подтягиваются к наилучшему сверху. Сравним следубщие примеры.

Пример 1:
```cpp=
template<typename T, typename U>
void f(T x, U y) {
    std::cout << 1 << '\n';
}

template<>
void f(int x, int y) {
    std::cout << 2 << '\n';
}

template<typename T>
void f(T x, T y) {
    std::cout << 3 << '\n';
}

int main() {
    f(0); // 3
}
```

Пример 2:

```cpp=
template<typename T, typename U>
void f(T x, U y) {
    std::cout << 1 << '\n';
}


template<typename T>
void f(T x, T y) {
    std::cout << 3 << '\n';
}

template<>
void f(int x, int y) {
    std::cout << 2 << '\n';
}

int main() {
    f(0); // 2
}
```

Пример 3:

```cpp=
template<typename T>
void f(T x, T y) {
    std::cout << 3 << '\n';
}

template<typename T, typename U>
void f(T x, U y) {
    std::cout << 1 << '\n';
}

template<>
void f(int x, int y) {
    std::cout << 2 << '\n';
}

int main() {
    f(0); // 2
}
```

### Частичная специализация шаблонов

В отличие от функций для классов возможна частичная специализация.

```cpp=
template<typename T, typename U>
class MyClass {
public:
    void f() {
        std::cout << 1 << '\n';
    }    
};

// partial specialization
template<typename T, typename U>
class MyClass<T&, U&> {
public:
    void f() {
        std::cout << 2 << '\n';
    }
};

// partial specialization
template<typename T>
class MyClass<T, T> {
public:
    void f() {
        std::cout << 3 << '\n';
    }    
};

int main() {
    MyClass<int, double> s;
    s.f(); // 1
    MyClass<int&, double&> ss;
    ss.f(); // 2
    MyClass<int, int> sss;
    sss.f(); // 3
    MyClass<int&, int&> ssss;
    ssss.f(); // CE
}
```

## Non-type шаблоны

В качестве параметра шаблона могут выступать не только типы, но и определенные значения типов. Следующий пример демонстрирует *описание* класса массивов с использованием шаблонов:
```cpp=
template<typename T, size_t N>
class array {
  T a[N];
};

template<typename T, *T P>
class MyClass {
  
};
```
Массивы одинаковых размеров складывать между собой мы можем и понимаем как это реализовывать. А вот операции с массивами разных размеров не поддерживаются. Мы не понимаем как работать с двумя *разными* типами.
В качестве определенных значений можно почти все (из этого все уходят типы с плавающей точкой и еще некоторые).
Параметр шаблона с определенным значением должен быть известен на этапе компиляции или же компилятор выдаст ошибку.
```cpp=
#include <iostream>
#include <array>

int f(int x) {
  return x * x;
}

int main() {
  std::array<int, 5> arr0;  // OK
  const int x = 5;
  std::array<int, x> arr1;  // OK, the second parameter is known before compilation
  const int y = f(2);
  std::array<int, y> arr2;  // CE, the second parameter need a calculation in run-time
 }
```
Значение шаблонного параметра должно быть зафиксировано в *момент компиляции*!

### Вычисления во время компиляции*

Покажем на примере вычисления чисел Фибоначчи как можно использовать шаблонные классы
```cpp=
#include <iostream>

template<int N>
struct Fibonacci {
  static const long long value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value
};

template<>
struct Fibonacci<1> {
  static const long long value = 1;
};

template<>
struct Fibonacci<0> {
  static const long long value = 0;
};

int main() {
  std::cout << Fibonacci<23> << "\n";
  return 0;
}
```
Если попытаться скомпилировать код без специализации шаблонов для 0 и 1, то получим выход за максимальное количество шагов рекурсии. Это получается из-за того, что на этапе компиляции компилятор пытается вычислить значения чисел Фибоначчи для каждого N и переместить их в статическое поле памяти.
Если же специализировать шаблоны начальными значениями, то программа скомпилируется и на этапе компиляции будут уже известны числа Фибоначчи
Ограничение на шаблонную рекурсию можно изменить, если передать в компилятор флаг `-ftemplate-depth 100`. Это изменит глубину шаблонной рекурсии до 100 (по умолчанию 900).

## Шаблонные параметры шаблонов

В качестве шаблонов могут выступать шаблоны. Синтаксис следующий:
```cpp=
#include <iostream>
#include <array>

template<typename T, template<typename> class Container>
struct Stack {
  Container<T> c;
};

int main() {
  Stack<int, std::array> s;
}
```

## Инстанцирование классов

Рассмотрим шаблонное наследование
```cpp=
template<typename T>
struct Base {
  int x;
};

template<typename T>
struct Derived: Base<T> {
  void f() {
    std::cout << x << '\n';
  }
};
```
Данный код скомпилируется с ошибкой. Компилятор не понимает в какой именно из копии шаблонов нужно взять `x`. Для явного указания можно воспользоваться `this` или же `Base<T>::x`

Компилятор собирает шаблонный код в два этапа: в момент *мета* обработки кода (проверка корректности выражений) и в момент подстановки шаблонных значений.

```cpp=
template<typename T>
struct Danger {
  int a[N];
};

template<typename T, int N>
struct S {
  void f() {
    Danger<N> d;
  }
  
  void g() {
    Danger<-1> d;
  }
};
```
Если явно не объявить использование класса `S`, то компилятор не выдаст ошибки. С *мета* точки зрения код класса `S` является корректным как для шаблона. Если же начать объявлять такой объект (а точнее подставлять шаблонные значения для класса), то компилятор выдаст ошибку при попытке вызова метода `g()`.
Пока метод не вызван, он **не истанцируется**. Ленивое инстанцирование шаблонов.
При объявлении указателя на шаблонный класс (или же ссылки) компилятор лишь проверяет корректность переданных шаблонных параметров, но не инстанцирует поля и методы класса.
Можно использовать объявленные шаблонные классы без их реализации для создания соответствующих указателей и ссылок.
```cpp=
template<typename T, int N>  // incomplete type
struct S;

int main() {
  S<int, -1>* ptr = nullptr;
  return 0;
}
```
Так называемые неполные типы.


## Шаблоны с переменным количеством аргументов

Шаблоны могут принимать переменное количество аргументов. Это позволяет написать свой собственный `print`. 
```cpp=
#include <iostream>

template<typename Head>
void print(const Head& head) {
  std::cout << head << "\n";
}

template<typename Head, typename ...Tail>
void print(const Head& head, Tail... tail) {
  std::cout << head << '\t';
  print(tail...);
}


template<typename T, int N>  // incomplete type
struct S;

int main() {
  print(12, "abc", "ichi");
  return 0;
}
```
В данном случае `Tail` является представителем переменного числа аргументов, пакет переменного числа параметров.
Переменное число параметров распаковывается с помощью последующего троеточия.

Размер пакета можно узнать с помощью `sizeof...()`. В качестве аргумента передается пакет (например, `Tail`).

Рассмотрим еще один пример использование шаблонов для определения одинаковых типов:
```cpp=
#include <iostream>

template<typename T, typename U>
struct is_same {
  static const bool value = false;
};

template<typename T>
struct is_same<T, T> {
  static const bool value = true;
};

template<typename First, typename Second, typename ...Tail>
struct is_homogeneous {
  static const bool value = std::is_same<First, Second>::value && is_homogeneous<Second, Tail...>::value;
};

template<typename T, typename U>
struct is_homogeneous<T, U> {
  static const bool value = is_same<T, U>::value;
};

int main() {
  std::cout << is_homogeneous<int, int, int>::value << std::endl;
  std::cout << is_homogeneous<int, char, int>::value << std::endl;
  return 0;
}

```

## Задание

Реализовать шаблонный класс матриц с операторами умножения, сложения, вычитания. Данные хранятся не в динамической памяти.

*Шаблон* кода:
```cpp=
template<typename Field, size_t M, size_t N>
class Matrix {
  
};

template<typename Field, size_t M, size_t N, size_t K>
Matrix<Field, M, N> operator*(const Matrix<Field, M, N>& lha, const Matrix<Field, N, K>& rha) {
  
}
```
Сохранять в `classwork05/matrix.cpp`