Вывод и наследование аргумента шаблона функции

#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 , вы бы увидели другое поведение.

  1. Только FOO : объявленный тип f является const Fooamp; , поэтому вы получаете typeinfo for Foo в обоих случаях.

  2. Только GENERIC : в print_typeid(foo); выведенном типе параметра шаблона есть Foo , поэтому вы получаете typeinfo for Foo . Но в print_typeid(bar); выводимом типе есть Bar , и вы получаете typeinfo for Bar .

  3. Оба FOO и GENERIC : верно, что не шаблонная функция превосходит шаблонную функцию, а более специализированная шаблонная функция превосходит менее специализированную шаблонную функцию по разрешению перегрузки, если в противном случае функции были бы неоднозначными. Но это правило срабатывает только тогда, когда неявные последовательности преобразования для обоих вызовов достаточно близки к одному и тому же, что ни один из них не может рассматриваться как лучший на основе типов аргументов и параметров.

Для print_typeid(foo) сначала компилятор выполняет вывод типа для шаблона функции, получая GENERIC=Foo . Таким образом, специализация шаблона функции — это потенциальная функция с сигнатурой void print_typeid(const Fooamp;); . Поскольку это идентично нетаблонной функции, нетаблонная функция выигрывает.

Для print_typeid(bar) компилятор снова выполняет вывод типа, на этот раз получая GENERIC=Bar . Специализация шаблона функции имеет подпись void print_typeid(const Baramp;); . Таким образом, вызов не шаблонной функции требует преобразования производной в базовую, где вызов специализации шаблона требует только преобразования квалификации (добавления const ). Преобразование квалификации выполняется лучше, поэтому шаблон выигрывает в разрешении перегрузки.