В целом, это давняя проблема с шаблоном и наследованием в целом.
Проблема в том, что шаблон работает с точными типами и не учитывает фактор наследования, поскольку эти два понятия несколько ортогональны, и поэтому попытка смешать одно и другое часто подвержено ошибкам.
Вы также можете проверить это с помощью методов:
template <class T>
int fooBar(T) { return 10; }
int fooBar(A) { return 20; }
B b;
fooBar(b); // this returns 10, because fooBar<T> is a better match (no conversion)
Теперь, касаясь ваших проблем, хотя я ценю различные решения, которые были предложены с использованием трюков enable_if
и is_base_of
, я отказываюсь от них как от практических. Смысл специализации заключается в том, что автору Foo
не нужно знать о том, как кто-то собирается специализировать ее класс в случае необходимости, просто для того, чтобы это было легко. В противном случае, если вам нужна дюжина специализаций, вы получите очень странный класс Foo
, это точно.
STL уже имел дело с подобными проблемами. Принятая идиома обычно состоит в том, чтобы обеспечить класс черт. Класс черт по умолчанию обеспечивает хорошее решение для всех, в то время как класс черт можно приспособить для удовлетворения своих потребностей.
Я думаю, что должен быть способ использования Concepts (то есть, если T определяет T :: fooBar (), затем использует его, в противном случае использует версию по умолчанию ...), но для специфической перегрузки метода это не требуется.
namespace detail { int fooBar(...) { return 10; } }
template <class T>
class Foo
{
public:
static int FooBar() { T* t(0); return ::detail::fooBar(t); }
};
А теперь, чтобы специализироваться на производных классах A:
namespace detail { int fooBar(A*) { return 20; } }
Как это работает? При рассмотрении перегрузок многоточие является последним из рассмотренных методов, поэтому подойдет любой, который удовлетворяет требованиям до этого, поэтому он идеально подходит для поведения по умолчанию.
Некоторые соображения:
пространство имен: в зависимости от того, будет ли использоваться идентификатор fooBar
или нет, вы можете предпочесть выделение в собственное пространство имен (или выделенное для класса Foo
), в противном случае создайте неквалифицированный вызов и пусть пользователь определит его в пространстве имен своего класса.
этот трюк работает только для наследования и вызова метода, он не работает, если вы хотите ввести специальные typedefs
вы можете передать больше шаблонов фактическому методу, например, реальный тип
Вот пример с шаблонными функциями
namespace detail { template <class T> int fooBar(...) { return 10; } }
template <class T>
int Foo<T>::FooBar() { T* t(0); return ::detail::fooBar<T>(t); }
namespace detail {
template <class T>
int fooBar(A*)
{
return T::FooBar();
}
}
А вот что будет:
struct None {};
struct A { static int FooBar() { return 20; } };
struct B: A {};
struct C: A { static int FooBar() { return 30; } };
int main(int argc, char* argv[])
{
std::cout << Foo<None>::FooBar() // prints 10
<< " " << Foo<A>::FooBar() // prints 20
<< " " << Foo<B>::FooBar() // prints 20
<< " " << Foo<C>::FooBar() // prints 30
<< std::endl;
}