Предскажите, будет ли выведен тип шаблонного параметра, если он встречается внутри квалифицированного имени параметра функции.
Тип обычно не будет выведен, если он встречается внутри зависимого квалифицированного имени, например в конструкции вида контейнер<T>::тип. Такая позиция является невыводимым контекстом: компилятор не пытается восстановить T по результирующему вложенному типу. Если T не встречается в других выводимых параметрах, вызов без явного указания типа завершится ошибкой.
Вывод аргументов шаблона строится по сопоставлению фактических аргументов с формой параметров, а не по произвольному анализу внутренних определений типов. Это позволяет сохранять предсказуемость компиляции и не требовать вычисления неизвестных вложенных типов во время вывода.
Разделение на выводимые и невыводимые контексты также предотвращает неоднозначность: компилятор не обязан угадывать, какое конкретное определение вложенного типа следует раскрыть, чтобы получить значение шаблонного параметра.
Рассмотрим шаблон функции, параметр которой имеет тип, зависящий от T:
Из аргумента 42 компилятор видит только итоговый тип параметра функции — int. Он не может однозначно заключить, что T равен int: теоретически Box<float>::type, Box<long>::type и другие специализации тоже могли бы быть определены как int.
В typename Box<T>::type имя Box<T>::type зависит от T. Чтобы узнать, что именно означает type, компилятор должен сначала подставить T и рассмотреть специализацию Box<T>. Поэтому T, находящийся в зависимом квалифицированном имени, не используется для вывода.
При вызове print(42) подходящей кандидатуре не удаётся вывести T, и такая функция исключается из рассмотрения из-за ошибки вывода. Это не тот случай, когда тело функции или контекст присваивания могут дополнительно определить параметр шаблона.
Явное указание print<int>(42) меняет ситуацию: T уже известен до проверки типа параметра, поэтому Box<int>::type раскрывается в int, и вызов становится корректным.
Если тот же параметр встречается в выводимом месте, тип можно получить оттуда:
Здесь T выводится по первому параметру, а второй параметр лишь проверяет, что после подстановки Box<T>::type совместим с переданным аргументом. Это полезный способ разделить место вывода типа и место проверки ограничения.
Важно не смешивать невыводимый контекст с ключевым словом typename. typename сообщает компилятору, что зависимое квалифицированное имя следует трактовать как тип; оно не делает параметр шаблона выводимым и не устраняет ограничения вывода.
В библиотечном адаптере нужно принять значение, тип которого определяется вложенным типом пользовательского traits-класса. Разработчик пытается вывести основной тип непосредственно из этого вложенного типа, но вызовы с обычными объектами не компилируются.
Возможны несколько вариантов:
T выводится напрямую. Это сохраняет автоматический вывод, однако может сделать сигнатуру менее компактной.Практичное решение — добавить выводимый параметр либо использовать отдельную перегрузку, принимающую исходный объект. В результате компилятор получает T из непосредственного типа аргумента, а зависимое вложенное имя используется только после подстановки. Это делает диагностику предсказуемой и не требует от пользователей писать шаблонный аргумент вручную.
1. Можно ли сделать такой параметр выводимым, убрав typename?
Нет. В зависимом квалифицированном имени typename обычно необходим для указания, что имя обозначает тип. Его удаление не превращает контекст в выводимый; чаще всего программа перестанет корректно разбираться синтаксически.
2. Почему компилятор не выводит T обратным анализом определения traits-класса?
Потому что вывод идёт от типа аргумента к шаблонному параметру, а не решает общее уравнение по произвольным специализациям. Разные значения T могут давать одинаковый итоговый тип Box<T>::type, а анализ всех специализаций был бы зависим от содержимого программы и потенциально неоднозначен.
3. Что изменится, если вложенное имя использовать только в возвращаемом типе?
Само по себе это также не позволит вывести T: тип, присутствующий только в возвращаемом типе, не выводится из присваивания или ожидаемого типа результата. T должен появиться в выводимом параметре функции либо быть задан явно; после этого зависимый возвращаемый тип можно сформировать подстановкой.