Урок по треку C++ 6 мин

Шаблоны и концепты

Вывод типов, специализация, SFINAE, variadic templates, концепты и requires в C++20.

Шаблон это рецепт, по которому компилятор генерирует код для конкретных типов. std::vector<int> и std::vector<std::string> два разных класса, созданных из одного шаблона. Ошибки типов ловятся при компиляции, а сгенерированный код так же быстр, как написанный руками.

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

Компилятор генерирует код в момент использования (инстанцирования), поэтому ему нужно видеть определение шаблона, а не только объявление. Отсюда правило: шаблоны определяют в заголовочных файлах. Каждая единица трансляции инстанцирует свои копии, а линковщик выкидывает дубликаты. Если инстанцирований много, растут время сборки и размер бинарника (code bloat). Помогают extern template и явное инстанцирование в одном .cpp.

Вывод типов

template <class T> void f(T x);
const int& r = 42;
f(r);        // T = int: ссылка и верхний const отбрасываются
template <class T> void g(T& x);
g(r);        // T = const int

Тот же механизм работает для auto. С C++17 вывод работает и для шаблонов классов (CTAD): std::vector v{1, 2, 3} даёт std::vector<int>, std::pair p(1, 2.5) даёт std::pair<int, double>.

Специализация

template <class T> struct Hash { std::size_t operator()(const T&) const; };
template <> struct Hash<bool> { /* полная специализация */ };
template <class T> struct Hash<T*> { /* частичная специализация для указателей */ };

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

Зависимые имена

Внутри шаблона имя, зависящее от параметра, компилятор не может разобрать до инстанцирования (двухфазный поиск имён):

  • typename T::value_type x;: без typename компилятор считает T::value_type значением, а не типом;
  • члены зависимой базы видны только через this-> или Base<T>::;
  • шаблонный член зависимого типа вызывают с template: obj.template get<0>().

Variadic templates и fold-выражения

template <class... Ts>
auto sum(Ts... xs) { return (xs + ... + 0); }   // C++17 fold
sizeof...(Ts);                                   // число аргументов

SFINAE и концепты

SFINAE (substitution failure is not an error): если подстановка типов в сигнатуру шаблона не удалась, кандидат молча выбрасывается из перегрузки. На этом строились std::enable_if и трюки с decltype, но код получался нечитаемым, а ошибки огромными.

C++20 дал концепты: именованные требования к типам.

template <class T>
concept Number = std::integral<T> || std::floating_point<T>;

template <Number T>
T twice(T x) { return x * 2; }

template <class T>
    requires requires(T a) { a.size(); }
auto length(const T& c) { return c.size(); }

Плюсы: требования видны в сигнатуре, ошибки короткие («не выполнен концепт Number»), перегрузка по концептам выбирает более строгий вариант. В стандартной библиотеке десятки готовых концептов в <concepts> и <iterator>: std::same_as, std::convertible_to, std::random_access_iterator, std::ranges::range.

if constexpr

if constexpr (cond) вычисляет условие при компиляции, и в шаблоне ветка с ложным условием не инстанцируется. Это заменяет многие перегрузки и специализации:

template <class T>
std::string describe(const T& x) {
    if constexpr (std::is_arithmetic_v<T>) return std::to_string(x);
    else return std::string(x);
}

Частые ошибки

  • Определение шаблона в .cpp и ошибка линковки «undefined reference».
  • Забытый typename перед зависимым типом.
  • Обращение к члену зависимой базы без this->.
  • Попытка частично специализировать функцию.
  • Слишком общий шаблон с forwarding-ссылкой, перехватывающий вызовы, предназначенные копирующему конструктору.

Что спросят на собесе

  • Почему шаблоны пишут в заголовках.
  • Специализация полная и частичная, перегрузка шаблонов.
  • SFINAE и чем концепты лучше.
  • Variadic templates и fold-выражения.
  • typename, template, this-> в зависимом контексте.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.