Подтвердить что ты не робот

Разрешение перегрузки, шаблоны и наследование

#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

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

4b9b3361

Ответ 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&)