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

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

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

7
задан user176168 31 January 2010 в 14:50
поделиться

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

--- Оригинальный ответ Если вы можете использовать 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>(); }
};
7
ответ дан 6 December 2019 в 15:22
поделиться

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

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
ответ дан 6 December 2019 в 15:22
поделиться

Вам нужно специализироваться на точный тип. Например Foo fooa; foo.foobar (); получит вам 20 . Или использовать Boost.type_traits AS @ Benoît.

0
ответ дан 6 December 2019 в 15:22
поделиться

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

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

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

1
ответ дан 6 December 2019 в 15:22
поделиться

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

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

int foo::foobar(A &someA);

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

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
ответ дан 6 December 2019 в 15:22
поделиться

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

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

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

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;
}
5
ответ дан 6 December 2019 в 15:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: