Что происходит при выводе одного параметра шаблона по двум аргументам, если полученные типы несовместимы?
Вывод шаблонного параметра завершается ошибкой: компилятор не выбирает общий тип автоматически. Для одного параметра все результаты вывода должны согласоваться по правилам конкретного контекста; если этого не происходит, шаблонная функция обычно исключается из набора кандидатов из-за ошибки вывода.
Шаблоны C++ изначально обеспечивали повторное использование алгоритмов при сохранении статической типобезопасности. Поэтому вывод типов не пытается произвольно преобразовать аргументы к некоторому общему типу: он сначала должен однозначно определить параметры шаблона.
Такой подход предотвращает скрытые преобразования, которые могли бы изменить смысл операции или привести к неожиданной потере данных. Неявные преобразования применяются после успешного вывода, если параметр уже известен.
Рассмотрим функцию, у которой один параметр шаблона используется для двух параметров функции. При передаче значений разных типов первый аргумент может дать один результат вывода, а второй — другой.
Если компилятор начнёт самостоятельно выбирать между этими типами или вычислять их общий тип, вызов станет зависеть от дополнительных правил преобразований. Это усложнит перегрузку и может незаметно изменить тип результата или поведение алгоритма.
На этапе вывода компилятор сопоставляет каждый аргумент с соответствующим параметром функции. Для параметра типа T аргумент типа int обычно предлагает T = int, а аргумент типа long — T = long. Эти выводы несовместимы, поэтому T не может быть выведен.
В первом вызове преобразование int к long не выполняется для того, чтобы сначала получить единый T: неявные преобразования не используются как средство согласования выводимых параметров. Во втором вызове T уже равен long, поэтому первый аргумент может быть преобразован к этому типу обычными правилами вызова функции.
Правила вывода зависят от формы параметра. Например, при передаче по значению верхнеуровневые const и ссылки обычно не участвуют в итоговом типе, а при передаче по ссылке квалификаторы и категории объекта могут иметь значение. Но общий принцип сохраняется: результаты вывода должны быть совместимы, а общий тип автоматически не вычисляется.
Если функция участвует в перегрузке, такая ошибка обычно является неудачей вывода, а не безусловной ошибкой компиляции: кандидат удаляется из набора рассматриваемых перегрузок. Если подходящей перегрузки больше нет, компилятор выдаёт итоговую ошибку вызова.
Когда нужен общий тип, его следует выразить в интерфейсе явно. Для этого применяют отдельные параметры типов с последующим std::common_type_t, явное указание типа или предварительное приведение. Выбор зависит от того, нужно ли сохранить исходные типы или намеренно перевести значения в единую типовую область.
В библиотечном алгоритме сравнения нужно поддержать вызов с целым и вещественным аргументами. Вариант с одним T прост и гарантирует одинаковый тип, но отвергает смешанные аргументы. Вариант с двумя параметрами типов принимает их, однако разработчику нужно отдельно определить тип сравнения и возможные потери точности.
Явное приведение на стороне вызывающего кода даёт максимальный контроль, но ухудшает удобство API и может привести к повторению преобразований в разных местах. Если смысл алгоритма действительно предполагает работу в общем типе, практичнее принять два типа и явно выбрать std::common_type_t с подходящими ограничениями.
Такое решение делает преобразование частью контракта библиотеки, а не случайным следствием вывода. В результате смешанный вызов становится поддерживаемым осознанно, а неоднозначные или потенциально опасные типы можно отклонить ограничением шаблона.
Да, если конфликт возник именно на этапе вывода шаблонных аргументов, это обычно является неудачей подстановки для данного кандидата. Компилятор продолжает проверять другие перегрузки. Ошибка становится диагностируемой только после того, как не остаётся подходящего кандидата либо возникает другая проблема разрешения перегрузки.
std::common_type автоматически?Нет. std::common_type — это отдельный типовой механизм, который нужно явно использовать в интерфейсе или теле шаблона. Обычный вывод аргументов не заменяет два несовместимых результата вывода вычислением общего типа.
При явном указании параметра шаблона этап его вывода для этого параметра больше не нужен. Компилятор проверяет аргументы уже относительно известного типа и может применить допустимые неявные преобразования. Поэтому вызов с int и long может стать корректным, если T заранее задан как long и оба аргумента могут быть приведены к нему.