Программирование C++ШаблоныРазработчик библиотек на C++

Как шаблон шаблонный параметр с одним типовым параметром может принять шаблон с дополнительным параметром п...

Как шаблон-шаблонный параметр с одним типовым параметром может принять шаблон с дополнительным параметром по умолчанию?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

В C++17 и новее шаблон-шаблонный параметр может принять шаблон, у которого есть дополнительные параметры с аргументами по умолчанию. При последующем создании специализации эти параметры получают значения по умолчанию. До C++17 такое сопоставление обычно требовало более строгого совпадения списков параметров.

Исторический контекст

Шаблоны стандартной библиотеки часто имеют дополнительные параметры, например аллокатор контейнера, хотя обычному обобщённому коду нужен только тип элемента. Строгое сопоставление шаблон-шаблонных параметров делало такие шаблоны неудобными для передачи в другие шаблоны.

В C++17 правила сопоставления были смягчены: учитываются параметры шаблона-аргумента, имеющие значения по умолчанию. Это позволяет использовать шаблон с расширенным интерфейсом там, где фактически требуется только его базовая форма.

Постановка проблемы

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

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

Подробное решение

Шаблон-шаблонный параметр описывает не конкретный тип, а форму шаблона, который будет передан внутрь другого шаблона. Если форма аргумента совместима с ожидаемой формой, его можно передать, даже когда у аргумента есть дополнительные параметры по умолчанию.

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

#include <memory> template<class T, class Alloc = std::allocator<T>> struct Buffer {}; template<template<class> class Container> struct Uses { Container<int> value; }; Uses<Buffer> instance; // C++17 и новее

В Uses<Buffer> шаблон Buffer сопоставляется с параметром Container, ожидающим один типовой параметр. При формировании Container<int> получается Buffer<int, std::allocator<int>>.

До C++17 подобное сопоставление не соответствовало прежним более строгим правилам. Поэтому переносимый код должен учитывать стандарт компиляции и поддержку реализации; иногда диагностика зависит от флага языка или версии компилятора.

Правило относится именно к совместимости списков параметров шаблонов. Оно не выполняет обычные неявные преобразования типов и не позволяет пропустить обязательный параметр. Также нужно различать типовые, нетиповые и шаблонные параметры: их виды и порядок должны оставаться совместимыми.

Альтернативой может быть шаблон-шаблонный параметр с параметр-паком, например форма, принимающая произвольное число типовых параметров. Это гибче, но слабее документирует ожидаемый интерфейс и может скрыть ошибочное использование. Явный адаптер или псевдоним лучше контролирует форму, но добавляет вспомогательный слой.

Ситуация из практики

В библиотеке есть обёртка над контейнером. Ей достаточно создавать контейнер по типу элемента, но пользователи должны передавать std::vector, у которого присутствует дополнительный параметр аллокатора.

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

Рациональный выбор — параметр одного типа с расчётом на параметры аргумента по умолчанию, если целевая версия языка начинается с C++17. Такой интерфейс выражает реальную потребность библиотеки, автоматически использует значения по умолчанию и сохраняет проверку формы на этапе передачи шаблона. Если нужна поддержка C++14, потребуется адаптер или более строгая конструкция, совместимая со старыми правилами.

Что кандидаты часто упускают

  1. Достаточно ли любого значения по умолчанию для совместимости?

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

  1. Означает ли такое сопоставление удаление дополнительного параметра из шаблона-аргумента?

Нет. Параметр остаётся частью шаблона-аргумента. При каждом использовании шаблон-шаблонный параметр подставляет переданные аргументы, а отсутствующие аргументы заполняются правилами самого шаблона-аргумента. Поэтому значение по умолчанию может зависеть от уже переданных параметров, как аллокатор зависит от типа элемента.

  1. Всегда ли код с таким сопоставлением одинаково компилируется во всех режимах?

Нет. Расслабленное сопоставление относится к правилам C++17 и новее, а старые компиляторы или их режимы могут применять прежние требования либо поддерживать новое поведение неполно. Для библиотечного кода нужно явно зафиксировать минимальный стандарт, проверить целевые компиляторы и при необходимости предоставить адаптер для более старых режимов.