История вопроса:
Шаблоны были добавлены в C++ для эффективной реализации универсальных алгоритмов и структур данных. С самого начала требовался механизм автоматического вывода типов по входным аргументам, чтобы сделать использование шаблонов удобнее разработчика.
Проблема:
Механизм подстановки не всегда очевиден: возникает неоднозначность с ссылками, маскированием типов, частичными специализациями, шаблонными параметрами-ссылками и константами. Иногда компилятор не может вывести тип или выводит неожиданный результат.
Решение:
Компилятор анализирует аргументы, сопоставляет их с шаблонными параметрами, учитывая правила cv-квалификаторов, ссылки и указатели. Недостатки и ограничения требуют явного указания типа, когда автоматическая подстановка невозможна.
Пример кода:
template<typename T> void func(T arg) { /* ... */ } func(10); // T deduced as int func("abc"); // T deduced as const char* // Отличие между T arg и T& arg: template<typename T> void printRef(T& arg); // Не примет временный объект!
Ключевые особенности:
Что будет, если шаблонная функция принимает T& и попытаться передать ей временный объект?
Компилятор не сможет сделать вывод типа, так как временный объект нельзя передать по неконстантной ссылке. Возникнет ошибка компиляции.
template<typename T> void foo(T& arg); foo(42); // Ошибка!
Как работает подстановка типов с массивами?
Массивы "разлагаются" до указателей при передаче по значению, но если шаблон принимает ссылку, сохраняется размер массива, что часто используют для реализации safe-size шаблонов.
template<typename T, size_t N> void arraySize(T (&arr)[N]) { std::cout << N; } int x[10]; arraySize(x); // Выведет 10
Почему не всегда корректно использовать auto для хранения результатов вызова шаблонных функций?
Auto при выводе типа может "срезать" const или ref-квалификатор, что иногда приводит к неожиданным ошибкам копирования или изменяемости.
auto x = funcReturningRef(); // x будет по значению, а не ссылка!
Шаблонная функция универсальной сортировки принимает T&, пользователь пробует передать временный объект. В результате код не компилируется, а ошибка вызывает ступор у разработчика.
Плюсы:
Минусы:
Сортировщик реализован через универсальные ссылки (T&&) с использованием std::forward, что позволяет грамотно работать и с lvalue, и с rvalue, следовательно повышать производительность и гибкость.
Плюсы:
Минусы: