Покажите, как разворачивание параметр-пака в списке базовых классов формирует тип, наследующийся от каждого элемента пака.
При разворачивании параметр-пака в списке базовых классов шаблонная конструкция создаёт отдельный базовый класс для каждого типа из пака. В результате специализация наследуется от всех этих типов, что позволяет статически собирать композиции политик или примесей без ручного перечисления базовых классов.
До появления вариадических шаблонов в C++11 код для работы с произвольным числом типов обычно строили через фиксированное число параметров или рекурсивные шаблоны. Это увеличивало объём кода и требовало отдельной логики завершения рекурсии.
Параметр-паки и их разворачивание решили задачу представления произвольного набора типов непосредственно в объявлении шаблона. Одним из применений стало формирование классов с несколькими статическими примесями.
Допустим, библиотечный тип должен получить набор независимых возможностей: журналирование, проверку доступа и сбор метрик. Количество таких возможностей заранее неизвестно и должно задаваться параметрами шаблона.
Если перечислять базовые классы вручную, интерфейс становится фиксированным. Если использовать композицию через динамические указатели, появляются косвенный вызов, управление временем жизни и дополнительные затраты. Ошибка в разворачивании пака может привести к отсутствию нужной функциональности, конфликтам имён или неоднозначному наследованию.
В списке базовых классов записывается шаблон базового типа, за которым следует многоточие. Эта конструкция означает: подставить шаблон для каждого элемента параметр-пака и получить последовательность базовых классов.
Для P компилятор формирует наследование от Layer<int>, Layer<double> и Layer<char>. Каждый из них является отдельным базовым подобъектом, а сама композиция полностью определяется на этапе компиляции.
Порядок типов в развёрнутом списке сохраняется. Однако это не превращает наследование в последовательность выполнения: конструкторы базовых классов выполняются в порядке объявления базовых классов, а не в порядке записей в списке инициализации конструктора.
Механизм удобен для политик и mixin-классов, когда каждая примесь предоставляет небольшой независимый интерфейс. Для пустых базовых классов компилятор часто может применить оптимизацию пустого базового класса, но стандарт не обязан устранять размер каждого такого подобъекта во всех случаях.
Есть и ограничения. Повторение одного и того же базового типа может сделать объявление класса некорректным или вызвать неоднозначность доступа. Одинаковые имена членов разных базовых классов также требуют явного уточнения через квалифицированное имя или using.
Такое наследование является статическим: набор возможностей фиксируется типом специализации. Если набор компонентов должен изменяться во время выполнения, лучше использовать композицию объектов, контейнер компонентов или динамический полиморфизм.
В библиотеке обработки сообщений нужно добавлять к адаптеру произвольные политики: журналирование, повторные попытки и измерение задержек. Каждая политика должна быть небольшим классом с одинаковым ожидаемым соглашением об интерфейсе.
Рассматривались три варианта. Ручное наследование от каждой политики было простым, но не масштабировалось. Хранение политик через указатели обеспечивало динамическую замену, однако добавляло косвенные вызовы и усложняло управление временем жизни. Рекурсивный шаблон-контейнер был гибким, но требовал дополнительной структуры типов и усложнял доступ к политикам.
Выбрано разворачивание пака в список базовых классов. Оно сохранило статическую диспетчеризацию, позволило компилятору устранить лишние пустые подобъекты в подходящих случаях и сделало набор политик частью типа адаптера. Для политик, которые должны заменяться без перекомпиляции, этот выбор был бы неподходящим.
Почему многоточие относится к Layer<Ts>, а не просто к имени базового шаблона?
Разворачивается не произвольный фрагмент, а конкретный шаблон выражения, содержащий параметр-пак. Запись Layer<Ts>... задаёт образец одного элемента развёртки: для каждого типа T из Ts создаётся Layer<T>. Само имя шаблона без сформированного аргумента не описывает последовательность базовых типов.
Что произойдёт, если разные базовые классы предоставляют член с одинаковым именем?
Обращение к такому имени через объект производного класса может стать неоднозначным. Обычно проблему решают квалификацией имени конкретного базового класса или объявлением выбранной версии через using. Если политики должны вызывать общий интерфейс, его часто задают единым контрактом и явно координируют вызовы в производном классе.
Сохраняется ли порядок типов пака как значимый порядок выполнения?
Порядок развёртывания сохраняется в полученном списке базовых классов, но сам по себе параметр-пак не задаёт алгоритм выполнения. При создании объекта базовые подобъекты инициализируются в порядке объявления базовых классов, затем инициализируются нестатические поля, а после этого выполняется тело конструктора. Поэтому порядок пакета можно использовать для структуры типа, но нельзя подменять им явную логику последовательного вызова операций.