#c #templates #inheritance #template-argument-deduction #type-deduction
#c #шаблоны #наследование #шаблон-аргумент-вывод #вывод типа
Вопрос:
Я думал, что будет вызвана перегрузка функции наиболее конкретными совпадающими типами аргументов, но, похоже, я не понимаю аспект вывода типа, когда шаблонные и унаследованные типы объединены.
Пример:
#include<iostream>
#include<typeinfo>
struct Foo {};
struct Bar : Foo {};
#ifdef FOO
void print_typeid( const Fooamp; f ) {
std::cout << "(F) typeid: " << typeid(f).name() << std::endl;
}
#endif // FOO
#ifdef GENERIC
template<typename Generic>
void print_typeid( const Genericamp; g ) {
std::cout << "(G) typeid: " << typeid(g).name() << std::endl;
}
#endif // GENERIC
int main( int argc, char *argv[] ) {
Foo foo; print_typeid(foo);
Bar bar; print_typeid(bar);
return 0;
}
Тестовые примеры
1. Определите только FOO
$ g -DFOO main.cpp -o foo amp;amp; ./foo
Вывод:
(F) typeid: 3Foo
(F) typeid: 3Foo
Для меня это имеет смысл, поскольку объекты foo
и bar
могут быть
передается как const Fooamp;
и поскольку во время компиляции нет понижения,
bar
должен быть идентифицирован как имеющий тип Foo
.
2. Определите только общий
$ g -DGENERIC main.cpp -o generic amp;amp; ./generic
Вывод:
(G) typeid: 3Foo
(G) typeid: 3Bar
Это также имеет смысл, поскольку оба foo
и bar
являются значениями lvalues и могут быть переданы функции, которая принимает общую ссылку на константу. При этом выводится фактический тип каждого объекта.
3. Определите как FOO, так и GENERIC
$ g -DFOO -DGENERIC main.cpp -o both amp;amp; ./both
Вывод:
(F) typeid: 3Foo
(G) typeid: 3Bar
Это меня смущает. Уже установив, что оба объекта могут быть переданы обеим функциям, я ожидал, что, поскольку const Fooamp;
это более конкретный совместимый тип для bar
этого, мы получили бы тот же результат, что и в случае 1. Почему это происходит?
Протестировано с использованием gcc 7.2 и clang 4
Ответ №1:
Это меня смущает. Уже установив, что оба объекта могут быть переданы обеим функциям, я ожидал, что, поскольку
const Fooamp;
это более конкретный совместимый тип дляbar
этого, мы получили бы тот же результат, что и в случае 1. Почему это происходит?
Но const Generic amp;
, когда Generic
выводится как Bar
, это лучшее соответствие (точное совпадение) для Bar
объекта, чем const Foo amp;
.
Таким образом, версия шаблона print_typeid()
является предпочтительной и выбирается при вызове с Bar
объектом.
Напротив, при вызове print_typeid()
с const Foo amp;
объектом обе версии совпадают, как точные совпадения, и версия, не являющаяся шаблонной, предпочтительнее версии шаблона.
Ответ №2:
Во-первых, typeid
действует полиморфно, только если аргумент имеет полиморфный тип класса. И ни Foo
ни Bar
не являются полиморфными, поскольку у них нет никаких виртуальных функций или виртуальных базовых классов. Таким образом, обе ваши print_typeid
функции не рассматривают фактический тип объекта, а только объявленный тип выражения. Если бы вы добавили их virtual ~Foo() = defau<
в class Foo
, вы бы увидели другое поведение.
-
Только
FOO
: объявленный типf
являетсяconst Fooamp;
, поэтому вы получаетеtypeinfo
forFoo
в обоих случаях. -
Только
GENERIC
: вprint_typeid(foo);
выведенном типе параметра шаблона естьFoo
, поэтому вы получаетеtypeinfo
forFoo
. Но вprint_typeid(bar);
выводимом типе естьBar
, и вы получаетеtypeinfo
forBar
. -
Оба
FOO
иGENERIC
: верно, что не шаблонная функция превосходит шаблонную функцию, а более специализированная шаблонная функция превосходит менее специализированную шаблонную функцию по разрешению перегрузки, если в противном случае функции были бы неоднозначными. Но это правило срабатывает только тогда, когда неявные последовательности преобразования для обоих вызовов достаточно близки к одному и тому же, что ни один из них не может рассматриваться как лучший на основе типов аргументов и параметров.
Для print_typeid(foo)
сначала компилятор выполняет вывод типа для шаблона функции, получая GENERIC=Foo
. Таким образом, специализация шаблона функции — это потенциальная функция с сигнатурой void print_typeid(const Fooamp;);
. Поскольку это идентично нетаблонной функции, нетаблонная функция выигрывает.
Для print_typeid(bar)
компилятор снова выполняет вывод типа, на этот раз получая GENERIC=Bar
. Специализация шаблона функции имеет подпись void print_typeid(const Baramp;);
. Таким образом, вызов не шаблонной функции требует преобразования производной в базовую, где вызов специализации шаблона требует только преобразования квалификации (добавления const
). Преобразование квалификации выполняется лучше, поэтому шаблон выигрывает в разрешении перегрузки.