Шаблоны
Инстанциация шаблонов, специализация, SFINAE, CRTP, вариадические шаблоны и метапрограммирование.
35 вопросов
JuniorТеорияОчень частоЧто такое шаблонный класс и шаблонная функция?
Что такое шаблонный класс и шаблонная функция?
Шаблон — заготовка, параметризованная типами или значениями: шаблонная функция порождает семейство функций, шаблонный класс — семейство классов. Компилятор инстанциирует конкретную версию при использовании — явно или через вывод.
Типичные ошибки
- ✗Размещать реализацию шаблона в .cpp-файле — компилятору нужно полное определение в точке инстанциации, поэтому шаблоны должны быть в заголовочных файлах
- ✗Путать параметры шаблона с параметрами времени выполнения
- ✗Считать, что одна инстанциация шаблона разделяется между единицами трансляции — каждая TU получает собственную копию (компоновщик объединяет их через weak-символы)
Уточняющие вопросы
- →В чём разница между параметром-типом и параметром-значением (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ТеорияЧастоПочему реализация шаблонного класса должна находиться в заголовочном файле?
Почему реализация шаблонного класса должна находиться в заголовочном файле?
Компилятору нужно полное определение шаблона в каждой точке инстанциации. Реализация в .cpp видна только своему TU, остальные TU получают ошибки компоновщика. Решение — определение в заголовке; альтернатива — явная инстанциация плюс extern template.
Типичные ошибки
- ✗Помещать реализацию в .cpp и получать ошибки компоновщика 'undefined reference'
- ✗Забывать, что явная инстанциация в одном .cpp +
extern templateв заголовках — допустимая, но часто игнорируемая альтернатива - ✗Не понимать, что это ограничение времени компиляции, а не недостаток дизайна языка
Уточняющие вопросы
- →Как ключевое слово
export(удалённое в C++11) пыталось решить эту проблему? - →Как модули C++20 меняют ситуацию?
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ТеорияЧастоКак работают вариадические шаблоны?
Как работают вариадические шаблоны?
Вариадические шаблоны принимают любое число типовых или нетиповых параметров через parameter pack (typename... Ts), раскрываемый через .... Рекурсия — классический способ обработки; fold expressions C++17 покрывают большинство случаев.
Типичные ошибки
- ✗Неправильное расположение
...— оно должно следовать после паттерна, а не предшествовать ему - ✗Не использовать свёртки там, где достаточно
(f(args), ...)или(args + ...) - ✗Смешивать пакеты параметров с непакетными параметрами в запутанном порядке — пакеты, как правило, должны быть последними
Уточняющие вопросы
- →Что такое fold expressions и каковы их четыре формы в C++17?
- →Как
std::tupleиспользует вариадические шаблоны внутри?
JuniorТеорияИногдаМожет ли конструктор быть шаблонной функцией?
Может ли конструктор быть шаблонной функцией?
Да — конструктор может быть шаблоном функции-члена, что позволяет создавать объект из разных типов аргументов. Но шаблонный конструктор никогда не выбирается как конструктор копирования: компилятор всегда предпочитает неявно сгенерированный.
Типичные ошибки
- ✗Ожидать, что шаблонный конструктор заменит конструктор копирования — это невозможно, потому что компилятор всегда генерирует настоящий конструктор копирования с более высоким приоритетом
- ✗Забывать, что вывод аргументов шаблона применяется к шаблонным конструкторам так же, как и к обычным шаблонным функциям
- ✗Не указывать
explicitу шаблонного конструктора, что приводит к нежелательным неявным преобразованиям
Уточняющие вопросы
- →Можно ли явно инстанциировать шаблонный конструктор?
- →Как CTAD (вывод аргументов шаблона класса, C++17) взаимодействует с шаблонными конструкторами?
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ТеорияИногдаЧто такое explicit template instantiation и когда она полезна?
Что такое explicit template instantiation и когда она полезна?
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?