Выбор явной специализации класса на основе производного типа - PullRequest
6 голосов
/ 22 января 2010

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

#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 ]

7 голосов
/ 22 января 2010

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

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

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

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

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;
}
1 голос
/ 22 января 2010

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

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; }
};
1 голос
/ 22 января 2010

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

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

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

0 голосов
/ 22 января 2010

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

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

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!
0 голосов
/ 22 января 2010

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

...