Как вывод типа по параметру функции-значению обрабатывает верхнеуровневые const и volatile аргумента?
При выводе типа для параметра функции, передаваемого по значению, верхнеуровневые const и volatile аргумента отбрасываются. Поэтому для аргумента типа const int шаблонный параметр обычно выводится как int, поскольку функция получает собственную копию значения.
Это правило относится только к квалификаторам самого объекта. Квалификаторы вложенных частей типа, например const у типа, на который указывает указатель, сохраняются.
Обобщённые функции должны работать с объектами, отличающимися только верхнеуровневой изменяемостью, без создания отдельных специализаций для int, const int и volatile int. Передача по значению не предоставляет функции доступ к исходному объекту, поэтому его верхнеуровневые квалификаторы не влияют на тип локальной копии.
Такое поведение поддерживает модель семантики значения: внутри функции параметр является отдельным объектом, который можно изменять независимо от исходного аргумента.
Если неверно предположить, что const всегда сохраняется при выводе, можно ошибочно ожидать разные специализации шаблона для int и const int. Это приводит к неправильным static_assert, неожиданному выбору перегрузок и ошибкам при попытке использовать выведенный тип как тип исходного объекта.
Важно отличать верхнеуровневый квалификатор от квалификатора вложенного типа. В const int квалифицирован сам объект, а в const int* — объект int, на который указывает указатель; во втором случае const является частью типа указателя и не отбрасывается.
Для параметра вида T компилятор выводит T из типа аргумента с учётом правил передачи по значению. Верхнеуровневые const и volatile удаляются, поэтому из const int получается T = int, а из volatile int также T = int.
Минимальный пример:
В by_value параметр получает копию, поэтому T равен int. В by_reference параметр связан с исходным объектом, и const становится частью выведенного типа T: это const int.
Для указателей нужно смотреть на уровень квалификации. Из аргумента const int* при параметре T выводится T = const int*: указатель копируется, но он по-прежнему указывает на константный int. В отличие от этого, верхнеуровневый int* const при передаче по значению превращается в обычный int*.
Ссылочные параметры используют другие правила. Для T& квалификаторы исходного объекта сохраняются, а для const T& вывод позволяет принять как константные, так и неконстантные объекты, при этом сам параметр остаётся ссылкой на константу. Поэтому выбор между значением и ссылкой — это не только вопрос стоимости копирования, но и способ управления выводом типов и доступом к исходному объекту.
В обобщённой функции разработчик добавляет проверку, что для константного аргумента шаблонный параметр обязан быть const int. Такая проверка ломается, если параметр объявлен по значению: const исчезает по правилам вывода, и специализация с T = const int никогда не возникает.
Рассматривались два варианта. Можно было использовать параметр по значению и вручную восстанавливать информацию о константности, но исходная константность при этом уже потеряна. Можно было принимать параметр по T& или const T&; этот вариант сохраняет связь с исходным объектом, но меняет требования к категории аргумента и может потребовать отдельной обработки временных объектов.
Выбранный вариант — const T&, если функции требуется только читать исходный объект, либо T&& с корректным выводом, если нужно сохранить категорию значения. В результате шаблон не делает ложных различий между копией и источником и явно выражает нужную семантику доступа.
Сохраняется ли const у указателя на константный объект?
Да. В типе const int* квалифицирован объект, на который указывает указатель, а не сам указатель. При выводе по значению копируется указатель типа const int*, поэтому const остаётся частью выведенного типа. Удаляется только верхнеуровневый квалификатор, например у int* const.
Одинаков ли результат для параметров T и const T?
Нет, нужно различать выведенный T и фактический тип параметра. Для параметра T из const int выводится T = int. Для параметра const T шаблонный параметр также обычно выводится как int, но итоговый параметр функции имеет тип const int. То есть квалификатор может задаваться самим шаблонным образцом, даже если он не входит в T.
Почему переход от значения к ссылке может изменить не только T, но и допустимые вызовы?
Потому что ссылка не создаёт независимую копию и должна быть привязана к объекту с подходящими свойствами. T& не может привязаться к временному объекту, тогда как параметр по значению может принять его. const T& принимает временные объекты и сохраняет константность в типе ссылки, а forwarding reference T&& дополнительно различает lvalue и rvalue через специальное правило вывода ссылок.
Следовательно, выбор формы параметра одновременно определяет сохранение квалификаторов, возможность привязки к временным объектам и набор допустимых специализаций шаблонной функции.