Выбор явной специализации класса на основе производного типа


Привет у меня возникли проблемы с выбором правильной версии шаблонного класса, который имеет явную специализацию. Я хочу выбрать специализацию, используя производный класс класса, используемого для специализации. Сценарий таков:

#include <stdio.h>

class A
{};

class B: public A
{};

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

// Explicit specialization for A
template<> int Foo< A >::FooBar( void ) { return 20; }

void main( void)
{
   Foo<B> fooB;

   // This prints out 10 instead of wanted 20 ie compiler selects the general version
   printf("%d", fooB.FooBar() );
}
Как я уже говорил в своих комментариях, я хочу, чтобы 20 было напечатано, потому что B является производным от A, но вместо этого печатается 10. Как мне получить специализацию под названием , не прибегая к написанию специализации для каждого из них и каждый производный класс (мой реальный сценарий имеет Много производных типов).
6 6

6 ответов:

---EDIT: NEW ANSWER давайте сделаем оригинальный подход более доступным для обслуживания. Все важные варианты выбора можно найти в определении Foo. Предполагается, что его легко поддерживать.

#include <boost/mpl/if.hpp>
#include  <boost/type_traits/is_base_of.hpp>
#include <iostream>

class A
{};

class B: public A
{};

class C{};
class D : public C{};
class E{};

struct DefaultMethod
{
    static int fooBar() { return 10; }
};
struct Method1
{
    static int fooBar() { return 20; }
};
struct Method2
{
    static int fooBar() { return 30; }
};

template<typename T, typename BaseClass, typename Choice1, typename OtherChoice>
struct IfDerivesFrom :
    boost::mpl::if_<
        typename boost::is_base_of<BaseClass, T>::type,
        Choice1,
        OtherChoice>::type
{
};

template<typename T>
struct Foo :
    IfDerivesFrom<T, A,
      Method1,
      IfDerivesFrom<T, C,
          Method2,
          DefaultMethod>
      >
{
};

int main()
{
    std::cout << Foo<A>::fooBar() << std::endl;
    std::cout << Foo<B>::fooBar() << std::endl;
    std::cout << Foo<C>::fooBar() << std::endl;
    std::cout << Foo<D>::fooBar() << std::endl;
    std::cout << Foo<E>::fooBar() << std::endl;

    return 0;
}

- - - ОРИГИНАЛЬНЫЙ ОТВЕТ Если вы можете использовать boost, вы можете сделать что-то вроде следующего:

#include  <boost/type_traits/is_base_of.hpp>

template<bool b>
class FooHelper
{
    int FooBar();
};
template<> FooHelper<true>::FooBar(){ return 20;}
template<> FooHelper<false>::FooBar(){ return 10;}

template<typename T>
class Foo
{
public:
   int FooBar(void) { return FooHelper<boost::is_base_of<A, T>::type::value>(); }
};

В целом, это давняя проблема с шаблоном и наследованием в целом.

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

Вы также можете проверить это с помощью методов:

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 уже занимался подобными проблемами. Общепринятой идиомой обычно является предоставление класса признаков. Класс признаков по умолчанию предоставляет хорошее решение для всех в то время как можно специализировать класс черт для удовлетворения своих потребностей.

Я думаю, что должен быть способ использования понятий (т. е., если 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), в противном случае сделайте безусловный вызов и позвольте пользователю определить его в пространстве имен своего класса.

  • Этот трюк работает только для наследования и вызова метода, он не работает, если вы хотите ввести специальные типы рифов

  • Вы можете передать больше шаблонов в фактический метод, например, реальный тип

Вот пример с шаблонными функциями

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;
}

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

template <class T, class U>
demo { };

template <class T>
demo<int> {};  // use in the case of demo<XXX, int> 

Глядя на ваш код, я немного удивлен, что он вообще компилируется. Я не уверен, что есть какой-то способ заставить вашу специализированную функцию быть вызванной. Обычно вы специализируете класс в целом:

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

template<>
class Foo<A> {
public:
    int FooBar() { return 20; }
};

В этом случае, что но это не принесет тебе никакой пользы. Производный объект можно преобразовать в базовый неявно,но этовсе равно преобразование. С другой стороны, неспециализированная версия шаблона может использоваться с без преобразования-и при выборе, какой из них использовать, компилятор рассматривает тот, который может быть создан без преобразования, как лучший выбор, чем тот, который требует неявного преобразования.

Вот решение, но оно не особенно приятно:

template<typename T>
class Foo
{
public:
  int FooBar(typename disable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 10; }
  int FooBar(typename enable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 20; }
};

Вам нужно специализироваться на точном типе. Например, Foo<A> fooA; fooA.FooBar(); даст вам 20. Или используйте boost.type_traits , Как показывает @Benoît.

В принципе, вы хотите иметь триггер специализации шаблона для производных классов.

Если вам не нужно, чтобы фактический класс был специализированным, просто функция (ы), кажется, что вы могли бы просто сделать:

int foo::foobar(A &someA);

Если вам действительно нужно, чтобы класс был специализированным, я думаю, что вы хотите посмотреть на интерфейсы и частный шаблон данных класса; более или менее, интерфейс "сводит" объект к шаблону-специализации распознанного типа, а затем вызывает через; Ала

int foo::foobar(A &someA)
{ return fooImpl<A>::foobar(someA); }
Но я полагаю, что это не совсем ответ на ваш вопрос, потому что он не поддерживает общий случай. Я полагаю, вы могли бы:
template<class T>
class foo
{
public: 
    int foobar(T &t);
    int foobar(A &a);
}

foo<A>::foobar(someA);
foo<F>::foobar(someA);
foo<not B>::foobar(someB); //Should trigger foobar(A &a), right?

Это позволило бы тогда признать B производным от A, все еще обеспечивая общий случай. Я думаю; я не проверял это.

Это не самый красивый, но я думаю, что у вас есть некоторая возможность для некоторых забавных вещей контроля доступа, как там, если вы специализируетесь на фактическом классе, как вы можете включать или не включать различные функции типа foobar (A &a), разрешающие или запрещающие использование различных деревьев наследования; для приведенного выше примера;

foo<C>::foobar(someF); //doesn't exist!