Да, это немного удивительно, но наследование и шаблон не так хорошо сочетаются, когда дело доходит до разрешения перегрузки.
Дело в том, что при оценке того, какую перегрузку следует выбрать, компилятор выбирает одинэто требует наименьшего количества преобразований (встроенных во встроенные, производных в базовые, вызовов неявных конструкторов или операторов преобразования и т. д.).Алгоритм ранжирования на самом деле довольно сложный (не все преобразования обрабатываются одинаково ...).
Как только перегрузки ранжируются, если два самых верхних ранжируются одинаково, а одно является шаблоном, тоШаблон отбрасывается.Однако, если шаблон занимает более высокое место, чем шаблон без шаблона (обычно меньше конверсий), тогда шаблон выбирается.
В вашем случае:
- для
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, но немного более громоздко.Идея та же, хотя аргумент с многоточием ...
имеет наихудший ранг, поэтому мы можем использовать выбор перегрузки для другой функции для выбора основного.