Почему компилятор выбирает эту функцию шаблона над перегруженной функцией без шаблона?

Использование VС++ 2010, учитывая следующее:

class Base { };
class Derived : public Base { };

template<class T> void foo(T& t);  // A
void foo(Base& base);              // B

Derived d;
foo(d);                            // calls A
foo(static_cast<Base&>(d));        // calls B

Я бы хотел, чтобы "B" вызывался выше. Я могу добиться этого с помощью нажатия Base, но почему это необходимо?

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

Ответ 1

При прочих равных условиях функции nontemplate предпочтительнее, чем функциональные шаблоны. Однако в вашем сценарии все вещи не равны: (A) является точным совпадением с T = Derived, но (B) требует преобразования аргумента из производной в базовую.

Вы можете обойти это для конкретных случаев (например, этот) с помощью SFINAE (сбой замены не является ошибкой), чтобы предотвратить (A) от экземпляра с типом, который получен из Base:

#include <type_traits>
#include <utility>

template <typename T>
typename std::enable_if<
    !std::is_base_of<Base, T>::value
>::type foo(T& x)
{
}

void foo(Base& x)
{
}

Ответ 2

Выбор шаблона выбирается, потому что он лучше соответствует при вызове с аргументом типа Derived, чем перегруженная версия. Вы можете использовать SFINAE, чтобы удалить версию шаблона из разрешения перегрузки, чтобы другая версия была выбрана при вызове с аргументами типа Base или Derived.

#include <type_traits>
#include <iostream>

class Base { };
class Derived : public Base { };

template<class T> 
typename std::enable_if<
  std::is_base_of<Base, T>::value == false
>::type
foo(T&)  
{ 
  std::cout << "template foo" << std::endl; 
}


void foo(Base&)
{ 
  std::cout << "non-template foo" << std::endl; 
}


int main()
{
  Derived d;
  Base b;

  foo( d );
  foo( b );
}