Разрешение перегрузки, шаблоны и наследование
#include <iostream>
struct A {};
struct B : public A {};
template<typename T>
void foo(const T &x) { std::cout << "Called template" << std::endl; }
void foo(const A &a) { std::cout << "Called A" << std::endl; }
int main()
{
foo(A());
foo(B());
return 0;
}
Отпечатки:
Called A
Called template
У меня создалось впечатление, что подходящая функция без шаблона всегда выбирается над функцией шаблона. Может ли кто-нибудь объяснить мне шаги разрешения, которые приводят к этому несколько неожиданному результату?
Ответы
Ответ 1
У меня создалось впечатление, что подходящая функция без шаблона всегда выбирается над функцией шаблона.
Это выполняется только в том случае, если шаблон и не-шаблон являются одинаково хорошими кандидатами. Поэтому для foo(A())
выбрано не-шаблон.
Однако, в случае foo(B())
, использование не-шаблона требует преобразования с производной базой. Таким образом, шаблон функции строго лучше, и, следовательно, он выбирается.
Шаблон foo
создается в void foo(const B&)
. Подумайте, как это будет выглядеть без шаблонов:
void foo(const B &x) { std::cout << "Called template" << std::endl; }
void foo(const A &a) { std::cout << "Called A" << std::endl; }
Я считаю, что вы согласитесь, что вызов foo(B())
должен однозначно выбрать первый. Именно поэтому выбран шаблон.
Ответ 2
n3376 13.3.3.1/6
Когда параметр имеет тип класса, а выражение аргумента имеет производный тип класса, неявная последовательность преобразований является преобразование из производного класса в базовый класс.
n3376 13.3.3.1/8
Если конверсии не требуются для сопоставления аргумента с параметром тип, неявная последовательность преобразования - это стандартное преобразование последовательность, состоящая из преобразования идентичности (13.3.3.1.1).
Преобразование идентичности имеет точную таблицу соответствия рангов в 13.3.3.1.1/таблицу 12, но результат на основе хуже, чем личность.
Итак, у компилятора есть только кандидаты в первом случае
// template after resolving
void foo(const A&)
// non-template
void foo(const A&)
Оба имеют ранжирование идентичности, но так как сначала является шаблоном функции, будет выбран второй.
А во втором случае
// template after resolving
void foo(const B&)
// non-template
void foo(const A&)
Только первый имеет ранг и будет выбран.
Ответ 3
Может кто-нибудь объяснить мне шаги разрешения, которые приводят к этому несколько неожиданному результату?
вы можете посмотреть Разрешение перегрузки на странице cppreference.com:
http://en.cppreference.com/w/cpp/language/overload_resolution
в частности, см. раздел "Ранжирование неявных последовательностей преобразования"
Расширение ответа:
Я попытался дать больше разъяснений с выдержкой из информации из вышеупомянутой ссылки:
Сам шаблон функции не является типом, функцией или любым другим объектом. Никакой код не генерируется из исходного файла, который содержит только определения шаблонов. Для того, чтобы какой-либо код появлялся, необходимо создать экземпляр шаблона: аргументы шаблона должны быть определены так, чтобы компилятор мог генерировать фактическую функцию (или класс из шаблона класса).
Для этого компилятор проходит через:
- функция поиска имени шаблона
- вывод аргумента шаблона
В данном случае у компилятора есть несколько определений функций-кандидатов, которые могут обрабатывать конкретный вызов функции. Этими кандидатами являются instannces функции шаблона, а также релевантные определения не-шаблонных функций в программе.
Но ответ на ваш вопрос на самом деле находится здесь:
Вывод аргумента шаблона происходит после поиска имени шаблона функции (который может включать зависящий от аргумента поиск) и до разрешения перегрузки.
Тот факт, что функция разрешения перегрузки выполняется после создания экземпляра функции шаблона, является причиной вывода вашего кода.
Теперь ваш конкретный случай проходит через разрешение перегрузки следующим образом:
Разрешение перегрузки:
Если [предыдущие] шаги производят более одной кандидатской функции, то выполняется перегрузка, чтобы выбрать функцию, которая будет фактически вызвана. В общем, функция-кандидат, параметры которой ближе всего к аргументам, является тем, который вызывается.,,.
...
F1 определяется как лучшая функция, чем F2, если неявные преобразования для всех аргументов F1 не хуже, чем неявные преобразования для всех аргументов F2, и 1) существует хотя бы один аргумент F1, неявное преобразование которого лучше соответствующего имплицитного преобразования для этого аргумента F2
....
.
.
Ранжирование неявных преобразований:
Каждому типу стандартной последовательности преобразования присваивается один из трех рангов:
1) Точное совпадение: не требуется преобразование, преобразование lvalue-to-rvalue, преобразование квалификации, определяемое пользователем преобразование типа класса в тот же класс
2) Продвижение: интегральное продвижение, продвижение с плавающей запятой
3) Преобразование: интегральное преобразование, преобразование с плавающей запятой, преобразование с плавающей интеграцией, преобразование указателя, преобразование указателя в элемент, логическое преобразование, пользовательское преобразование производного класса в его базу
Ранг стандартной последовательности преобразования является наихудшим из рангов стандартных преобразований, которые он удерживает (может быть до трех преобразований)
Связывание ссылочного параметра непосредственно с выражением аргумента - это либо идентификатор, либо преобразование с производной на базовую:
struct Base {};
struct Derived : Base {} d;
int f(Base&); // overload #1
int f(Derived&); // overload #2
int i = f(d); // d -> Derived& has rank Exact Match
// d -> Base& has rank Conversion
// calls f(Derived&)