C ++: перегрузка не выбирает ожидаемый метод - PullRequest
0 голосов
/ 29 марта 2012

У меня есть следующий код:

#include <iostream>
#include <vector>
using namespace std;

struct A{};
struct B: public A {};

template <typename T>
void foo(const T& obj) { cerr << "Generic case"<< endl;}

void foo(const A& a) {
    cerr << "Specific case" << endl;
}

int main() {
    vector<int> v;
    foo(v);
    B b;
    foo(b);
    A a;
    foo(a);
}

Вывод

  • Универсальный регистр
  • Универсальный регистр
  • Конкретный случай

Почему foo(const A& a) не выбран для объекта B?

Как ни странно, если я удалил шаблонный метод и просто имею следующее:

#include <iostream>
#include <vector>

struct A{};
struct B: public A {};

//template <typename T>
//void foo(const T& obj) { cerr << "Generic case"<< endl;}

void foo(const A& a) {
    cerr << "Specific case" << endl;
}

int main() {
    B b;
    foo(b);
    A a;
    foo(a);
}

Код скомпилируется и выдает:

Specific case
Specific case

Почемуналичие шаблонного метода, имеющего такую ​​разницу?

Редактировать: Как я могу заставить компилятор выбирать свободный метод для классов, производных от A, при наличии шаблонного метода?

Ответы [ 3 ]

11 голосов
/ 29 марта 2012

Для вызова foo(const B&) преобразование не требуется, что дает создание шаблона, таким образом, оно лучше соответствует.

Когда компилятор видит вызов функции, каждый базовый шаблон функции должен быть создан и включен в набор перегрузки вместе с каждой нормальной функцией. После этого выполняется разрешение перегрузки. Существует также SFINAE, которая позволяет создание экземпляра шаблона функции, чтобы привести к ошибке (такая функция не будет добавлена ​​в набор перегрузки). Конечно, все не так просто, но это должно дать общую картину.

Относительно вашего редактирования: есть только один метод для вызова. Что еще может быть в качестве вывода?

4 голосов
/ 29 марта 2012

Да, это немного удивительно, но наследование и шаблон не так хорошо сочетаются, когда дело доходит до разрешения перегрузки.

Дело в том, что при оценке того, какую перегрузку следует выбрать, компилятор выбирает одинэто требует наименьшего количества преобразований (встроенных во встроенные, производных в базовые, вызовов неявных конструкторов или операторов преобразования и т. д.).Алгоритм ранжирования на самом деле довольно сложный (не все преобразования обрабатываются одинаково ...).

Как только перегрузки ранжируются, если два самых верхних ранжируются одинаково, а одно является шаблоном, тоШаблон отбрасывается.Однако, если шаблон занимает более высокое место, чем шаблон без шаблона (обычно меньше конверсий), тогда шаблон выбирается.

В вашем случае:

  • для std::vector<int> только одинперегрузки совпадают, поэтому он выбран.
  • для A две перегрузки совпадают, они ранжируются одинаково, шаблон один отбрасывается.
  • для B две перегрузки совпадают, шаблон ранг выше(преобразование из производной в базовую не требуется), оно выбрано.

Существует два обходных пути, самый простой - «исправить» сайт вызова:

A const& ba = b;
foo(ba);

Другой способ - исправить сам шаблон, однако это сложнее ...

Вы можете жестко указать, что для классов, производных от A, это не та перегрузка, которую вы хотите:

template <typename T>
typename std::enable_if<not std::is_base_of<A, T>::value>::type
foo(T const& t) {
  std::cerr << "Generic case\n";
}

Однако это не так гибко ...

Другое решение - определить крючок.Сначала нам понадобится некоторая утилита метапрограммирования:

// Utility
template <typename T, typename Result = void>
struct enable: std::enable_if< std::is_same<T, std::true_type>::value > {}; 

template <typename T, typename Result = void>
struct disable: std::enable_if< not std::is_same<T, std::true_type>::value > {}; 

А затем мы определим наш хук и функцию:

std::false_type has_specific_foo(...);

template <typename T>
auto foo(T const& t) -> typename disable<decltype(has_specific_foo(t))>::type {
  std::cerr << "Generic case\n";
}

И затем для каждого базового класса мы хотим определенный foo:

std::true_type has_specific_foo(A const&);

В действии на ideone .

Это возможно и в C ++ 03, но немного более громоздко.Идея та же, хотя аргумент с многоточием ... имеет наихудший ранг, поэтому мы можем использовать выбор перегрузки для другой функции для выбора основного.

1 голос
/ 29 марта 2012

@ pmr answer объясняет, почему шаблонная функция является предпочтительной в вашем примере. Чтобы заставить компилятор выбрать вашу перегрузку, вы можете использовать SFINAE, чтобы удалить шаблонную функцию из набора перегрузки. Измените шаблон foo на

template <typename T>
typename std::enable_if<!std::is_base_of<A, T>::value>::type
  foo(const T& obj) { cerr << "Generic case"<< endl;}

Теперь, если T равно A или класс, полученный из A, тип возвращаемого значения для шаблонной функции недопустим, и он будет исключен из разрешения перегрузки. enable_if присутствует в заголовке type_traits.

...