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

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

#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 ответов

Решение

--- РЕДАКТИРОВАТЬ: НОВЫЙ ОТВЕТ Давайте сделаем оригинальный подход более ремонтопригодным. Все важные варианты можно найти в определении 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;
}

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

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

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

  • этот трюк работает только для наследования и вызова метода, он не работает, если вы хотите ввести специальные 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;
}

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

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

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

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

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

Вам нужно специализироваться на точном типе. Например 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!
Другие вопросы по тегам