Шаблоны
Инстанцирование шаблонов, специализация, SFINAE, CRTP, вариативные шаблоны и метапрограммирование.
38 вопросов
JuniorТеорияОчень частоЧто такое шаблоны классов и шаблоны функций?
Что такое шаблоны классов и шаблоны функций?
Заготовки, по которым компилятор создаёт отдельную функцию или класс для каждого используемого типа
Типичные ошибки
- ✗Ждать выбора во время выполнения: компилятор создаёт отдельную версию для каждого типа ещё при компиляции, и при запуске выбирать уже нечего.
- ✗Считать шаблон макросом: шаблоны обрабатывает компилятор, а не препроцессор, и тело каждой версии он проверяет для её типа.
Уточняющие вопросы
- →В чём разница между параметром-типом и параметром-значением (non-type template parameter)?
- →Когда лучше использовать шаблон функции вместо обычной перегрузки?
JuniorТеорияОчень частоЧто такое инстанцирование шаблона? Неявное vs явное?
Что такое инстанцирование шаблона? Неявное vs явное?
Инстанцирование шаблона — генерация компилятором конкретного класса или функции подстановкой аргументов. Неявное происходит автоматически при первом использовании; явное форсит его в заданном TU и с extern template подавляет неявное в других.
Типичные ошибки
- ✗Не знать, что неявно инстанцируются только используемые члены — неиспользуемые методы шаблонного класса не компилируются
- ✗Размещать явное инстанцирование в заголовочных файлах — это должно быть ровно в одном .cpp-файле
- ✗Забывать
extern templateдля подавления неявного инстанцирования во всех TU при использовании явного инстанцирования для сокращения времени компиляции
Уточняющие вопросы
- →Как
extern templateсокращает время компиляции? - →В чём разница между определением и объявлением шаблона?
MiddleТеорияОчень частоКаковы правила вывода типов в шаблонах?
Каковы правила вывода типов в шаблонах?
Вывод типов в шаблонах в трёх случаях: по значению (top-level квалификаторы отбрасываются), по ссылке (квалификаторы сохраняются), по универсальной ссылке T&& (lvalue-ссылка через свёртку). Массивы и функции «распадаются» в указатели при выводе по значению.
Типичные ошибки
- ✗Ожидать, что
constсохранится при передаче по значению — вывод по значению всегда отбрасывает верхнеуровневые cv-квалификаторы - ✗Путать
T&&(универсальная/forwarding-ссылка) с rvalue-ссылкой — это зависит от контекста - ✗Игнорировать «распад» массива в указатель:
template<typename T> void f(T x)сf(arr)выводитT = int*, а неint[N]
Уточняющие вопросы
- →Как вывод типа
autoсоответствует выводу в шаблонах? В чём отличие (brace-init)? - →Что такое свёртка ссылок и когда она происходит?
JuniorТеорияЧастоЧто произойдёт при сборке и запуске этой программы?
Что произойдёт при сборке и запуске этой программы?
Не скомпилируется: T не может быть одновременно int и double
Типичные ошибки
- ✗Ждать
2.5: при выводе преобразования не применяются, поэтомуnдаётT=int,x—T=double, и они не совпадают. - ✗Ждать
2: у первого аргумента нет приоритета — каждый аргумент выводитTсам, и результаты должны совпасть.
Уточняющие вопросы
- →Как изменить вызов, чтобы программа скомпилировалась и вывела
2.5? - →Почему обычная функция
double max_of(double a, double b)принимает тот же вызов?
JuniorТеорияЧастоПочему реализация шаблонного класса должна находиться в заголовочном файле?
Почему реализация шаблонного класса должна находиться в заголовочном файле?
Компилятору нужно полное определение шаблона в каждой точке инстанцирования. Реализация в .cpp видна только своему TU, остальные получают ошибки компоновщика. Решение — определение в заголовке; альтернатива — явное инстанцирование и extern template.
Типичные ошибки
- ✗Помещать реализацию в .cpp и получать ошибки компоновщика 'undefined reference'
- ✗Забывать, что явное инстанцирование в одном .cpp +
extern templateв заголовках — допустимая, но часто игнорируемая альтернатива - ✗Не понимать, что это ограничение времени компиляции, а не недостаток дизайна языка
Уточняющие вопросы
- →Как ключевое слово
export(удалённое в C++11) пыталось решить эту проблему? - →Как модули C++20 меняют ситуацию?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
iT
Открыть задачу →Типичные ошибки
- ✗Ждать
ii: специализация подходит только для своего типа. Когда компилятор выбирает версиюTag, типshortне продвигается доint, иTag<short>строится из основного шаблона. - ✗Ждать
TT: уTag<int>есть полная специализация, и дляintона заменяет основной шаблон.
Уточняющие вопросы
- →Как дать
Tagотдельную версию дляdouble? - →Почему у полной специализации могут быть члены, которых нет у основного шаблона?
JuniorТеорияЧастоЧто такое специализация шаблона? Полная vs частичная?
Что такое специализация шаблона? Полная vs частичная?
Специализация даёт шаблону другую реализацию для конкретных аргументов. Полная фиксирует все параметры и пишется через template<>; частичная фиксирует часть или ограничивает их — только у шаблонов классов и переменных, не функций.
Типичные ошибки
- ✗Попытка частично специализировать шаблонную функцию — вместо этого используйте перегрузку или
if constexpr - ✗Определять в заголовке без
inlineполную специализацию шаблона функции или переменной — ошибки множественного определения (полной специализации шаблона класса это не грозит) - ✗Неправильный порядок специализаций — первичный шаблон должен быть объявлен до любой специализации
Уточняющие вопросы
- →Когда явная специализация предпочтительнее
if constexprвнутри шаблона? - →Как компилятор выбирает между первичным шаблоном и его специализациями?
MiddleТеорияЧастоЧто такое fold expressions и какие у них формы?
Что такое fold expressions и какие у них формы?
Fold expressions (C++17) сворачивают parameter pack бинарным оператором в четырёх формах: unary right (args op ...), left (... op args), binary right (args op ... op init), left (init op ... op args). Identity пустого pack — для &&, ||, ,.
Типичные ошибки
- ✗Складывать
+пустой pack — ошибка, нет identity - ✗Путать направление left/right fold с ассоциативностью оператора
- ✗Заворачивать не-pack выражение в fold — работают только packs
Уточняющие вопросы
- →Как реализовать
print(args...)через fold и<<? - →Почему
(args, ...)работает на пустом pack?
MiddleТеорияЧастоЧто делает if constexpr и чем он отличается от обычного if?
Что делает if constexpr и чем он отличается от обычного if?
if constexpr (C++17) отбрасывает невыбранную ветку при компиляции — она не обязана компилироваться при текущем инстанцировании, что позволяет одной шаблонной функции обрабатывать пути по типу. Обычный if требует компиляции обеих веток.
Типичные ошибки
- ✗Использовать
if constexprс рантайм-условием — нельзя, требуется compile-time - ✗Забывать
elseи получать непреднамеренный fall-through после зависимого кода - ✗Класть неотбрасываемый код в ветку (
static_assert) — сработает в любом случае
Уточняющие вопросы
- →Как
if constexprработает сstatic_assert? - →Почему
if constexprчище tag-dispatch во многих случаях?
MiddleТеорияЧастоЧто такое non-type template parameter и какие виды разрешены?
Что такое non-type template parameter и какие виды разрешены?
Non-type-параметр принимает значение, не тип (template<size_t N> struct Array {T data[N];}). До C++20: integral, enum, указатели/ссылки с linkage, nullptr_t. C++17 добавил auto; C++20 — structural class types вроде строк фиксированного размера.
Типичные ошибки
- ✗Пытаться передать
std::stringкак non-type параметр до C++20 — нельзя - ✗Забыть
auto(C++17), когда тип сложный или вычисляемый - ✗Перепутать порядок типовых и non-type параметров
Уточняющие вопросы
- →Как fixed-string в C++20 работают как параметры шаблона?
- →Что такое structural equality и зачем оно для class non-type params?
MiddleТеорияЧастоКак работает раскрытие parameter pack и какие паттерны через него выражают?
Как работает раскрытие parameter pack и какие паттерны через него выражают?
Паттерн с ... повторяется для каждого элемента pack: f(args)... вызывает f для каждого arg, Ts... раскрывает type pack, (args + ...) — fold. Допустимы трансформации вроде f(g(args)...); std::index_sequence распаковывает tuple.
Типичные ошибки
- ✗Забыть
...в конце паттерна — раскрывается только первый элемент - ✗Путать fold expression
(args + ...)и pack expansionargs... - ✗Пытаться раскрыть pack вне контекста, который это разрешает (обычное присваивание)
Уточняющие вопросы
- →Как
std::applyиспользуетindex_sequenceдля распаковки tuple? - →Почему
(args = 0, ...)валиден, аargs = 0...нет?
MiddleТеорияЧастоКаковы правила свёртки ссылок и почему forwarding-ссылки от них зависят?
Каковы правила свёртки ссылок и почему forwarding-ссылки от них зависят?
Когда ссылки накладываются, они сворачиваются: любая комбинация с lvalue-ссылкой даёт T&, и только T&& && даёт T&&. Forwarding-ссылка T&& выводит T как U& для lvalue, так что U& && сворачивается в U&; для rvalue T = U, давая U&&. Так сохраняется категория значения.
Типичные ошибки
- ✗Называть
T&&rvalue-ссылкой, когдаT— выводимый параметр шаблона; это forwarding-ссылка - ✗Считать
const T&&илиvector<T>&&forwarding-ссылкой — только чистый выводимыйT&&ею является - ✗Использовать
std::moveвместоstd::forwardв forwarding-контексте, молча перемещая из lvalue
Уточняющие вопросы
- →Почему
auto&&тоже forwarding-ссылка, аconst auto&&— нет? - →Как
std::forward<T>использует выведенныйTдля восстановления категории значения?
MiddleТеорияЧастоЧто такое SFINAE и для чего используется?
Что такое SFINAE и для чего используется?
SFINAE (Substitution Failure Is Not An Error): если подстановка аргументов шаблона в immediate context даёт некорректный тип или выражение, компилятор молча убирает перегрузку. Лежит в основе std::enable_if для условного выбора перегрузок.
Типичные ошибки
- ✗Попытка вызвать SFINAE в теле функции — он работает только в непосредственном контексте подстановки параметров шаблона, но не в теле
- ✗Использовать SFINAE там, где концепты C++20 дали бы более чистый код и понятные сообщения об ошибках
- ✗Писать глубоко вложенные цепочки
enable_if, которые становятся нечитаемыми — лучше использоватьif constexprдля ветвлений внутри функции
Уточняющие вопросы
- →Как концепты C++20 заменяют большинство сценариев применения SFINAE?
- →Что такое
std::void_tи как с его помощью обнаруживать члены типов?
SeniorТеорияЧастоЧто такое C++20 концепты и как они заменяют SFINAE?
Что такое C++20 концепты и как они заменяют SFINAE?
Концепт — именованный compile-time предикат над параметрами шаблона (template<Number T> T add(T,T);). Ошибки становятся читаемыми — вместо страниц SFINAE-фейла «T не удовлетворяет Number». Концепты ранжируют перегрузки по subsumption.
Типичные ошибки
- ✗Писать концепты, не проверяющие что нужно — концепт проходит для нежеланных типов
- ✗
requires (T t) { t + t; }проверяет компилируемость, не семантику - ✗Смешивать SFINAE и концепты в одном overload set — путаница приоритетов
Уточняющие вопросы
- →Чем requires-clause отличается от requires-expression?
- →Как subsumption концептов ранжирует перегрузки?
SeniorТеорияЧастоЧем requires-clause отличается от requires-expression?
Чем requires-clause отличается от requires-expression?
requires-clause ограничивает шаблон (requires Number<T>); requires-expression — compile-time проверка валидности операций (requires (T t) { t+t; }). Комбинируются как requires requires (T t) { ... } — внешний clause, внутренний expression.
Типичные ошибки
- ✗Использовать синтаксис expression там, где ждут clause (после
template<>или сигнатуры) - ✗Забывать, что expression проверяет компилируемость, а не семантическую корректность
- ✗Оборачивать один концепт в
requires (T t) { ... }когдаrequires Concept<T>короче
Уточняющие вопросы
- →Какие виды clause бывают в requires-expression (simple, type, compound, nested)?
- →Когда subsumption выбирает одну constrained-перегрузку над другой?
SeniorТеорияЧастоЧто такое статический полиморфизм и как он реализуется в C++?
Что такое статический полиморфизм и как он реализуется в C++?
Статический полиморфизм выбирает поведение по типам, известным на compile time, с нулевой стоимостью диспетчеризации. Реализуется через перегрузку, специализацию шаблонов, CRTP, if constexpr и концепты. Не работает с гетерогенными коллекциями.
Типичные ошибки
- ✗Считать, что статический полиморфизм всегда быстрее — увеличенный размер кода из-за инстанцирования шаблонов может вызвать давление на кэш инструкций
- ✗Выбирать CRTP вместо виртуальных функций, когда тип неизвестен на этапе компиляции
- ✗Путать
std::variant+std::visit(полиморфизм с закрытым набором типов во время выполнения) со статическим полиморфизмом
Уточняющие вопросы
- →Когда следует выбирать
std::variant+std::visitвместо полиморфизма на основе наследования? - →Что такое стирание типов и как
std::functionего реализует?
SeniorТеорияЧастоКак работают вариативные шаблоны?
Как работают вариативные шаблоны?
Вариативные шаблоны принимают любое число типовых или нетиповых параметров через пакет параметров (typename... Ts), раскрываемый через .... Раньше пакет обрабатывали рекурсией, с C++17 обычно хватает выражений свёртки.
Типичные ошибки
- ✗Ставить
...не на то место при раскрытии пакета — оно идёт после шаблона выражения, как вf(args)... - ✗Не использовать свёртки там, где достаточно
(f(args), ...)или(args + ...) - ✗Смешивать пакеты параметров с непакетными параметрами в запутанном порядке — пакеты, как правило, должны быть последними
Уточняющие вопросы
- →Что такое выражения свёртки (fold expressions) и каковы их четыре формы в C++17?
- →Как
std::tupleиспользует вариативные шаблоны внутри?
JuniorТеорияИногдаМожет ли конструктор быть шаблонной функцией?
Может ли конструктор быть шаблонной функцией?
Да — конструктор может быть шаблоном функции-члена для разных типов аргументов. Шаблон не бывает конструктором копирования, неявный всё равно объявляется, но template<class T> X(T&&) побеждает его при копировании неконстантного lvalue.
Типичные ошибки
- ✗Считать, что неявный конструктор копирования всегда выигрывает — для неконстантного lvalue
template<class T> X(T&&)выводитX&, лучшее совпадение, и перехватывает копирование - ✗Забывать, что вывод аргументов шаблона применяется к шаблонным конструкторам так же, как и к обычным шаблонным функциям
- ✗Не указывать
explicitу шаблонного конструктора, что приводит к нежелательным неявным преобразованиям
Уточняющие вопросы
- →Можно ли явно инстанцировать шаблонный конструктор?
- →Как CTAD (вывод аргументов шаблона класса, C++17) взаимодействует с шаблонными конструкторами?
JuniorТеорияИногдаЧто выведет программа?
Что выведет программа?
3 0
Открыть задачу →Типичные ошибки
- ✗Ждать размера в байтах:
sizeof...(Ts)считает элементы пакета (здесь три), а не складывает их размеры, как сделал быsizeof. - ✗Ждать, что
count_args()не скомпилируется: пакет параметров может быть пустым, и тогдаsizeof...(Ts)равно0.
Уточняющие вопросы
- →Как вернуть сумму всех аргументов с помощью выражения свёртки (fold expression)?
- →Чем
sizeof...(Ts)отличается отsizeof(T)для одного типаT?
JuniorТеорияИногдаМожет ли виртуальная функция быть шаблонной?
Может ли виртуальная функция быть шаблонной?
Нет — виртуальные функции не могут быть шаблонами. Vtable требует фиксированного compile-time размера, а шаблон функции может порождать неограниченное число экземпляров, известных лишь на этапе компоновки.
Типичные ошибки
- ✗Путать виртуальную функцию в шаблонном классе (разрешено) с шаблонной виртуальной функцией-членом (запрещено)
- ✗Пытаться обойти ограничение, делая шаблонным весь класс — это работает, но требует знания всех типов на этапе определения
Уточняющие вопросы
- →Как добиться полиморфизма по разным типам без шаблонных виртуальных функций (стирание типов, std::function, std::variant)?
- →Что такое CRTP и как он обеспечивает полиморфизм на этапе компиляции?
MiddleТеорияИногдаКак концепты C++20 ограничивают параметры и возвращаемые типы auto?
Как концепты C++20 ограничивают параметры и возвращаемые типы auto?
Префикс концепта перед auto ограничивает его: void f(Sortable auto& c) делает параметр сокращённым шаблоном, чей выведенный тип обязан удовлетворять Sortable. То же для возвращаемых типов: Number auto g();. Несовпадение убирает функцию из overload resolution, а не даёт жёсткую ошибку.
Типичные ошибки
- ✗Думать, что
Sortable autoименует фиксированный тип — это всё ещё выводимый параметр сокращённого шаблона - ✗Ожидать, что нарушение ограничения — жёсткая ошибка, а не просто удаление перегрузки
- ✗Забывать, что каждый constrained
autoвводит независимый параметр шаблона
Уточняющие вопросы
- →Чем сокращённый шаблон функции отличается от явно записанного
template<>? - →Что происходит, когда подходят две constrained-перегрузки — как решает subsumption?
MiddleТеорияИногдаЧто такое class template argument deduction (CTAD) и deduction guides?
Что такое class template argument deduction (CTAD) и deduction guides?
CTAD (C++17) позволяет писать std::vector v{1,2,3}; и компилятор сам выводит std::vector<int>. Guides синтезируются из конструкторов; если мало — пользовательские: template<class It> Container(It,It) -> Container<typename It::value_type>;.
Типичные ошибки
- ✗Ждать CTAD от alias до C++20 — не работало
- ✗Писать deduction guides неверной arity и ломать неявные
- ✗Забывать, что
auto v = std::vector{1, 2, 3};тоже выводит — CTAD работает
Уточняющие вопросы
- →Что изменилось для CTAD в C++20 (alias templates)?
- →Как CTAD работает с explicit-конструкторами?
MiddleДебаггингИногдаПочему сообщения об ошибках в шаблонах такие длинные и как их укротить?
Почему сообщения об ошибках в шаблонах такие длинные и как их укротить?
Ошибки рождаются глубоко в цепочке инстанцирования — компилятор печатает каждый шаг. Что помогает: C++20 концепты дают однострочное «не удовлетворяет X», ранний static_assert в точке вызова даёт понятное сообщение, и помогает cppinsights.io.
Типичные ошибки
- ✗Читать ошибки сверху вниз вместо начала с вашей строки
- ✗Игнорировать
note:, объясняющие, почему substitution failed - ✗Городить ещё SFINAE вместо рефакторинга
Уточняющие вопросы
- →Как концепты делают ошибки более понятными?
- →Что показывает
cppinsights.io?
MiddleТеорияИногдаЧто такое alias-templates и чем они отличаются от typedef?
Что такое alias-templates и чем они отличаются от typedef?
Alias template (template<class T> using Vec = std::vector<T, MyAlloc>;) — параметризованное имя типа; typedef не принимает параметров шаблона. Алиасы также избавляют от typename dependent-name проблем. C++20 добавил CTAD для alias.
Типичные ошибки
- ✗Пытаться сделать параметризованный typedef — нужен alias template
- ✗Забывать, что CTAD для alias — C++20; старый код не выведет через alias
- ✗Алиас не создаёт новый тип — это лишь имя
Уточняющие вопросы
- →Почему alias не создают отдельные типы (vs
struct StrongType { T x; };)? - →Какую проблему решает CTAD-for-aliases в C++20?
MiddleТеорияИногдаЧто такое variable template и чем он отличается от шаблона функции или класса?
Что такое variable template и чем он отличается от шаблона функции или класса?
Variable template (C++14) — параметризованная константа: template<class T> constexpr T pi = T(3.14159);. Каждое использование с типом инстанцирует отдельный объект. В отличие от шаблона функции он не вызывается; в отличие от шаблона класса он именует значение, а не тип.
Типичные ошибки
- ✗Забыть
inlineилиconstexprу variable template в заголовке — риск нарушения ODR между TU - ✗Считать, что частичная специализация запрещена — variable templates её поддерживают, как и классы
- ✗Думать, что
pi<int>иpi<double>делят хранилище, хотя каждый экземпляр — отдельный объект
Уточняющие вопросы
- →Как
std::is_integral_v<T>связан сstd::is_integral<T>::value? - →Можно ли частично специализировать variable template и зачем?
SeniorТеорияИногдаЧто такое CRTP и какие проблемы он решает?
Что такое CRTP и какие проблемы он решает?
CRTP (Curiously Recurring Template Pattern): Derived наследуется от Base<Derived>, чтобы Base вызывал методы производного через static_cast<Derived*>(this) без виртуальной диспетчеризации. Применения: статический полиморфизм, mixins.
Типичные ошибки
- ✗Вызывать методы производного класса в конструкторе базового — производный объект ещё не создан
- ✗Забывать, что CRTP не поддерживает гетерогенные коллекции так, как это делает виртуальная диспетчеризация
- ✗Оставлять невиртуальным деструктор базового класса — безопасно для CRTP, поскольку удаление через указатель на базовый класс не используется, но стоит помнить
Уточняющие вопросы
- →Как явный параметр
this(deducing this, C++23) снижает потребность в CRTP? - →Сравните CRTP-примеси с концептами C++20 для контроля интерфейса.
SeniorДебаггингИногдаПочему имя из зависимого базового класса не разрешается без this-> или using-объявления?
Почему имя из зависимого базового класса не разрешается без this-> или using-объявления?
При поиске фазы 1 компилятор не просматривает зависимые базы, ведь специализация могла бы изменить содержимое Base<T>. Поэтому неквалифицированное value трактуется как не-член и не находится. Сделав его зависимым — this->value или Base<T>::value — мы откладываем поиск до инстанцирования, когда база известна.
Типичные ошибки
- ✗Добавлять
this->лишь там, куда указывает ошибка, пропуская другие неквалифицированные имена базы - ✗Считать код переносимым, раз MSVC его компилирует — MSVC исторически пропускает строгий поиск фазы 1
- ✗Думать, что независимая база (
Base<int>) имеет ту же проблему — она есть только у зависимых баз
Уточняющие вопросы
- →Почему GCC и Clang отвергают этот код, а старый MSVC принимает?
- →Когда
Base<T>::valueпредпочтительнееthis->value?
SeniorТеорияИногдаЧто такое detection idiom и как std::void_t его реализует?
Что такое detection idiom и как std::void_t его реализует?
Detection idiom проверяет на этапе компиляции, корректно ли выражение вроде T::member для конкретного типа, не порождая жёсткой ошибки компиляции. std::void_t<...> отображает любые валидные типы в void, а частичная специализация по void_t<expr> матчится только когда expr компилируется — иначе выбирается запасной первичный шаблон.
Типичные ошибки
- ✗Помещать проверяемое выражение вне immediate context, где сбой становится жёсткой ошибкой вместо тихого несовпадения
- ✗Забывать, что второй аргумент частичной специализации должен быть именно
void— посторонний не-void ломает матч - ✗Тянуться к detection idiom в коде C++20, где
requires-выражение или концепт намного понятнее
Уточняющие вопросы
- →Как
requires-выражение C++20 заменяет detection idiom? - →Почему проверяемое выражение должно быть в immediate context?
SeniorТеорияИногдаЧто такое явное инстанцирование шаблона и когда оно полезно?
Что такое явное инстанцирование шаблона и когда оно полезно?
template class Foo<int>; форсит инстанцирование шаблона здесь, генерируя объектный код. Парно с extern template в заголовке, чтобы потребители не инстанцировали. Ускоряет сборку, когда много TU делят один экземпляр.
Типичные ошибки
- ✗Использовать
extern templateбез явного инстанцирования где-то — link error - ✗Смешивать extern template и полное определение в заголовках — легко сломать ODR
- ✗Забывать, что явное инстанцирование требует полного определения в точке инстанцирования
Уточняющие вопросы
- →Как explicit instantiation помогает ABI shared-библиотеки?
- →Что такое two-phase name lookup и как связано с extern template?
SeniorТеорияИногдаКак компилятор обрабатывает шаблоны (двухфазный поиск имён)?
Как компилятор обрабатывает шаблоны (двухфазный поиск имён)?
Шаблоны обрабатываются в две фазы. Фаза 1 при определении: проверяется синтаксис, ищутся независимые имена. Фаза 2 при инстанцировании: зависимые имена ищутся в контекстах определения и инстанцирования.
Типичные ошибки
- ✗Забывать
typenameперед зависимым именем типа — компилятор по умолчанию считает его значением, а не типом - ✗Забывать
templateперед зависимым именем шаблона, используемым как шаблон:obj.template method<T>() - ✗Полагаться на ADL в фазе 2 для нахождения имён, которые должны были быть найдены в фазе 1
Уточняющие вопросы
- →Что такое ADL (поиск, зависящий от аргументов) и как он взаимодействует с шаблонами?
- →Почему MSVC может принять код, который GCC и Clang отвергают из-за двухфазного поиска?
SeniorТеорияИногдаЧто такое метапрограммирование на шаблонах?
Что такое метапрограммирование на шаблонах?
TMP использует шаблоны C++ как движок compile-time вычислений — типы и значения через рекурсивное инстанцирование (по-старому) или constexpr (по-новому). <type_traits> — лицо TMP в STL; концепты C++20 упрощают паттерны.
Типичные ошибки
- ✗Писать глубоко рекурсивный TMP там, где
constexpr+ свёртки дают то же самое с долей сложности - ✗Игнорировать стоимость времени компиляции — тяжёлый TMP может значительно замедлить сборку
- ✗Изобретать заново примитивы
<type_traits>вместо комбинирования из стандартной библиотеки
Уточняющие вопросы
- →Как концепты C++20 заменяют
std::enable_ifв большинстве случаев? - →В чём разница между
constexprи TMP для вычислений на этапе компиляции?
SeniorТеорияИногдаЧто такое tag dispatch и чем он отличается от if constexpr или concepts?
Что такое tag dispatch и чем он отличается от if constexpr или concepts?
Tag dispatch выбирает перегрузку через пустую тег-структуру категории — классический пример STL: std::distance диспетчеризуется по iterator_category. Современный C++ заменяет это на if constexpr или constraint-based перегрузки.
Типичные ошибки
- ✗Определять теги без наследования — ломает subsumption (random_access is a forward)
- ✗Использовать tag dispatch, где
if constexprпроще - ✗Забывать объявить перегрузку, принимающую тег — caller не может диспетчеризовать
Уточняющие вопросы
- →Как iterator_category образует иерархию через наследование?
- →Когда concept-based dispatch чище tag dispatch?
SeniorТеорияИногдаКак реализуется рекурсия в шаблонах и что её заменяет в современном C++?
Как реализуется рекурсия в шаблонах и что её заменяет в современном C++?
Классический TMP использовал рекурсивное инстанцирование: основной шаблон плюс специализация base case, Factorial<N>::value = N * Factorial<N-1>::value; с Factorial<0>::value = 1. Современный C++ заменяет это if constexpr и consteval.
Типичные ошибки
- ✗Глубокая шаблонная рекурсия раздувает время компиляции и память
- ✗Забыть специализацию base case — бесконечная рекурсия в компиляторе
- ✗Использовать рекурсивные шаблоны там, где проще fold expressions или
consteval
Уточняющие вопросы
- →Как реализовать compile-time фибоначчи через
consteval? - →Почему шаблонная рекурсия считается медленной в compile-time?
SeniorТеорияИногдаКогда и зачем нужны disambiguator'ы typename и template внутри шаблонов?
Когда и зачем нужны disambiguator'ы typename и template внутри шаблонов?
Внутри шаблона компилятор не различает, dependent-имя — тип, значение или шаблон; по умолчанию считает значением. typename T::nested объявляет тип; t.template foo<int>() объявляет шаблон. C++20 сделал typename опциональным.
Типичные ошибки
- ✗Забыть
typenameи получить непонятные «expected primary-expression» - ✗Забыть
.templateпри вызове шаблонного метода на dependent-объекте - ✗Ставить
typenameтам, где не нужен — до C++20 безвредно; в C++20 может быть лишним
Уточняющие вопросы
- →Где конкретно C++20 сделал
typenameопциональным? - →Зачем парсеру вообще эти подсказки?
SeniorПроизводительностьРедкоЧто делает extern template и как ускоряет компиляцию?
Что делает extern template и как ускоряет компиляцию?
extern template class Foo<int>; в заголовке подавляет неявное инстанцирование во всех включающих TU; один .cpp предоставляет тело через template class Foo<int>;. Избавляет от повторного парсинга и кодогенерации в каждом TU.
Типичные ошибки
- ✗Ставить
extern templateбез парного явного инстанцирования — link error - ✗Забывать, что
extern templateподавляет только неявное инстанцирование - ✗Добавлять
extern templateдля шаблонной функции в заголовке, где TU могут её inline'ить
Уточняющие вопросы
- →Как
extern templateработает с header-only библиотеками? - →Почему стандартная библиотека использует
extern templateдля распространённых экземпляров шаблонов?
SeniorТеорияРедкоКак компилятор выбирает наиболее специализированный шаблон среди конкурирующих кандидатов?
Как компилятор выбирает наиболее специализированный шаблон среди конкурирующих кандидатов?
Partial ordering ранжирует шаблоны по специфичности: A специфичнее B, если любой набор аргументов, подходящий A, подходит и B, но не наоборот. Компилятор проверяет это, выводя параметры каждого кандидата из другого, и побеждает односторонне выводимый кандидат. Правило применяется и к частичным специализациям классов, и к перегрузкам шаблонных функций.
Типичные ошибки
- ✗Считать, что победителя определяет порядок объявления — partial ordering зависит только от специфичности, не от позиции
- ✗Писать две частичные специализации, ни одна из которых не специфичнее другой — это ошибка неоднозначности
- ✗Ждать, что partial ordering функций работает как частичная специализация классов — у функций вместо неё перегрузка
Уточняющие вопросы
- →Почему две частичные специализации могут быть взаимно неоднозначными?
- →Как partial ordering взаимодействует с поглощением концептов в C++20?
SeniorТеорияРедкоКак корректно объявить шаблонного друга внутри класса?
Как корректно объявить шаблонного друга внутри класса?
Три формы: (1) один экземпляр — friend void f<int>(MyClass&);; (2) весь шаблон — template<class T> friend void f(MyClass&);; (3) определить нешаблонную friend внутри тела класса — паттерн для operator<<, чтобы ADL её находил.
Типичные ошибки
- ✗Подружить один экземпляр шаблона, считая, что доступ получили все остальные
- ✗Определить
operator<<вне template-класса — name lookup не находит, т.к. не объявлен как нешаблонная - ✗Забыть синтаксис
template<>для дружбы с конкретным экземпляром шаблона
Уточняющие вопросы
- →Почему
operator<<для template-классов часто определяют внутри класса? - →Как ADL находит friend-функции?
SeniorТеорияРедкоЧто такое template template parameter и где оно полезно?
Что такое template template parameter и где оно полезно?
Template template parameter принимает шаблон, а не тип: template<template<typename> class C> struct Wrap { C<int> v; };. Полезен в коде, заново инстанцирующем generic-контейнер с разными типами элементов — например, политики аллокатора.
Типичные ошибки
- ✗Забывать, что arity внутреннего шаблона должна совпадать с тем, чем инстанцируете
- ✗Использовать
classкак ключ-слово до C++17 — толькоclass; теперь оба - ✗Путать template template params и variadic template templates
Уточняющие вопросы
- →Как принять
std::vectorиstd::listединообразно? - →Может ли template template parameter быть
auto?