Вывод аргументов шаблона функции и наследование - PullRequest
0 голосов
/ 02 марта 2019

Я думал, что будет вызвана перегрузка функции с наиболее конкретными совпадающими типами аргументов, но, похоже, я не понимаю аспект вывода типов, когда шаблон и наследуемые типы объединяются.

Пример:

#include<iostream>
#include<typeinfo>

struct Foo {};
struct Bar : Foo {};

#ifdef FOO
void print_typeid( const Foo& f ) {
  std::cout << "(F) typeid: " << typeid(f).name() << std::endl;
}
#endif // FOO

#ifdef GENERIC  
template<typename Generic>
void print_typeid( const Generic& 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 && ./foo

Вывод:

(F) typeid: 3Foo
(F) typeid: 3Foo

Это имеет смысл для меня, поскольку объекты foo и bar могут быть переданыкак const Foo&, и поскольку во время компиляции нет понижения, bar должен быть идентифицирован как имеющий тип Foo.

2.Определить только GENERIC

$ g++ -DGENERIC main.cpp -o generic && ./generic

Вывод:

(G) typeid: 3Foo
(G) typeid: 3Bar

Это также имеет смысл, так как foo и bar являются lvalues ​​и могут бытьпередается в функцию, которая принимает общую константную ссылку.Это печатает фактический тип каждого объекта.

3.Определите FOO и GENERIC

$ g++ -DFOO -DGENERIC main.cpp -o both && ./both

Вывод:

(F) typeid: 3Foo
(G) typeid: 3Bar

Это меня смущает.Уже установив, что оба объекта могут быть переданы обеим функциям, я ожидал, что, поскольку const Foo& является более конкретным совместимым типом для bar, мы получили бы тот же вывод, что и в случае 1. Почему это происходит?

Протестировано с использованием gcc 7.2 и clang 4

Ответы [ 2 ]

0 голосов
/ 03 марта 2019

Во-первых, typeid действует только полиморфно, если аргумент имеет тип полиморфного класса.И ни Foo, ни Bar не являются полиморфными, поскольку они не имеют никаких виртуальных функций или виртуальных базовых классов.Таким образом, обе ваши print_typeid функции не смотрят на фактический тип объекта, а только на объявленный тип выражения.Если вы добавите virtual ~Foo() = default; в класс Foo, вы увидите другое поведение.

  1. Only FOO: Объявленный тип f равен const Foo&, поэтому вы получитеtypeinfo для Foo в обоих случаях.

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

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

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

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

0 голосов
/ 02 марта 2019

Это меня смущает.Уже установив, что оба объекта могут быть переданы обеим функциям, я ожидал, что, поскольку const Foo& является более конкретным совместимым типом для bar, мы получили бы тот же вывод, что и в случае 1. Почему это происходит?

Но const Generic &, когда Generic выводится как Bar, является лучшим совпадением (точным совпадением) для объекта Bar, чем const Foo &.

Таким образом, версия шаблона print_typeid() является предпочтительной и выбирается при вызове с объектом Bar.

Напротив, вызов print_typeid() с объектом const Foo &, обе версии совпадают, как точные совпаденияи версия без шаблона предпочтительнее версии шаблона.

...