Полиморфизм времени компиляции C++

Есть две несвязанные структуры A и B

template <typename T>
struct A {};

template <typename T>
struct B {};

один тип перечисления

typedef enum  { ma, mb} M;

и класс C, содержащий шаблоны функций

class C
{
public: 
    template <typename T>
    static void f1 ( A <T> &a) {}

    template <typename T>
    static void f2 ( B <T> &b) {}

    template <typename U>
    static void algo (U &u, M m)
    {
        /*Long algorithm here                     
        ....
        */
        if ( m == ma) f1(u);
        else f2(u);
    }
};

Алгоритм статического метода содержит некоторый алгоритм, который довольно сложен... Он изменил некоторые значения и результаты в структуру A или B.

Я хотел бы запустить алгоритм статического метода с объектами A или B в зависимости от значения M. Но как сказать это моему компилятору :-)

int main()
{
A <double> a;
C::algo (a, ma); //Error

}

Error   1   error C2784: 'void C::f1(A<T>)' : could not deduce template argument for 'A<T>' from 'B<T>

A] Я думал об указателе на функцию, но его нельзя использовать с шаблонами функций.

B] Возможно, полиморфизм компиляции может помочь

template <typename U, M m>
static void algo (U &u, M <m> ) { ...}  //Common for ma

template <typename U, M m>
static void algo (U &u, M <mb> ) { ...} //Spec. for mb

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

Поэтому мне нужна одна функция algo(), обрабатывающая оба типа аргументов A и B. Есть ли более удобное решение?


person justik    schedule 22.01.2012    source источник
comment
Кстати, это полиморфизм. Полиморфизм - это нечто другое (Полли, больше рыбы? Хм?).   -  person Lightness Races in Orbit    schedule 22.01.2012
comment
@ Als: я не проверял название :-)   -  person justik    schedule 22.01.2012
comment
@LightnessRacesinOrbit О боже, я смеялся! Теперь на моем экране молоко.   -  person user703016    schedule 22.01.2012
comment
@justik: Действительно, нет. Я заметил, что вы правильно написали это в теле вопроса, поэтому я предположил, что это опечатка, и исправил для вас. Надеюсь, вы не возражаете, даже если вы это сделаете, я не изменю его обратно на offering fish to Rocko's Polly;)   -  person Alok Save    schedule 22.01.2012
comment
@Als: Он написал это правильно один раз и один раз неправильно в теле вопроса.   -  person Lightness Races in Orbit    schedule 22.01.2012


Ответы (4)


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

В самом простом случае, если f1 и f2 переименовать в f, то можно вообще удалить if и просто вызвать его. Компилятор вызовет для вас соответствующую перегрузку.

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

Если перечисление используется для чего-то другого (которое компилятор не может разрешить за вас), вы все равно можете передать его и переписать помощник для отправки перечисления, а не типа аргумента, и вам придется переписать код, чтобы иметь значение перечисления как константа времени компиляции (самый простой: передать его как аргумент шаблона в algo). В этом случае вы можете написать специализации функций вместо классов, если хотите, так как они будут полными специализациями. Но учтите, что если вы сможете избежать его передачи, вы устраните целое семейство ошибок: передачу неправильного значения перечисления.

// Remove the enum and rename the functions to be overloads:
//
struct C {  // If everything is static, you might want to consider using a
            // namespace rather than a class to bind the functions together...
            // it will make life easier

   template <typename T>
   static void f( A<T> & ) { /* implement A version */ }

   template <typename T>
   static void f( B<T> & ) { /* implement B version */ }

   template <typename T> // This T is either A<U> or B<U> for a given type U
   static void algo( T & arg ) {
      // common code
      f( arg ); // compiler will pick up the appropriate template from above
   } 
};

Для других альтернатив проще, если охватывающая область является пространством имен, но идея будет той же (только, возможно, потребуется немного сложнее бороться с синтаксисом:

template <typename T>
struct dispatcher;

template <typename T>
struct dispatcher< A<T> > {
   static void f( A<T>& arg ) {
      C::f1( arg );
   }
};
template <typename T>
struct dispatcher< B<T> > {
   static void f( B<T>& arg ) {
      C::f2( arg );
   }
};

template <typename T>
void C::algo( T & arg ) {
   // common code
   dispatcher<T>::f( arg );
}

Опять же, заставить это работать с классом может быть немного сложнее, поскольку для этого, вероятно, потребуется пара предварительных объявлений, и у меня нет под рукой компилятора, но набросок должен привести вас в правильном направлении.

person David Rodríguez - dribeas    schedule 22.01.2012
comment
@ Дэвид Родригес. Могу я попросить вас привести примеры: случай B] и C]? Большое спасибо за Вашу помощь. - person justik; 22.01.2012
comment
@ Дэвид Родригес: Это действительно помогло, еще раз спасибо... Я изучаю второй пример... - person justik; 22.01.2012

Достаточно обычной перегрузки функций:

template <typename T> 
static void f1 ( A <T> &a) {} 

template <typename T> 
static void f2 ( B <T> &b) {} 

template <typename T> 
static void algo (A<T>& u) {
    f1(u);
} 

template <typename T> 
static void algo (B<T>& u) {
    f2(u);
} 

А потом:

A<int> a;
Foo::algo(a);

Хотя непонятно, что вы выиграете от такой договоренности.

person Jon    schedule 22.01.2012
comment
@ Джон: Спасибо, но мне нужна одна функция, чтобы не писать один и тот же код (алгоритм) дважды. - person justik; 22.01.2012
comment
@justik, может быть, тебе стоит просто переименовать f2 в f1. Тогда algo будет просто вызовом f1. template <typename T> static void f1 ( A <T> &a) {} template <typename T> static void f1 ( B <T> &b) {} - person Aaron McDaid; 22.01.2012
comment
@ Аарон МакДейд: Хорошо, но ситуация немного другая. В длинном алгоритме мне нужно хранить результаты в двух структурах A или B, используя f1 или f2. Мне нужно только одно тело функции... - person justik; 22.01.2012
comment
@justik: вам нужно использовать обе структуры во всех случаях или вам нужно использовать любую структуру в зависимости от аргументов? Это очень разные цели. Проблема с вашим вопросом заключается в том, что неясно, каковы ваши требования. Мне кажется, что предложение Аарона в комментарии имеет смысл. - person David Rodríguez - dribeas; 22.01.2012

Если вам действительно нужно сделать это в одной функции, вы можете использовать typetraits:

 template<typename T, T Val>
 struct value_type { static const T Value = Val; };

 struct true_type   : public value_type<bool, true>{};
 struct false_type  : public value_type<bool, false>{};


 template<class T>
 struct isClassA : public false_type{};

 template<>
 struct isClassA<A> : public true_type{};


 template < typename T >
 void Algo( T& rcT )
 {
    if ( true == isClassA<T>::Value )
    {
        // Class A algorithm
    }
    else
    {
        // Other algorithm
    }
 };
person Martin Brandl    schedule 22.01.2012
comment
@ jisaak: Спасибо, но алгоритм предотвращает это условие. Так что тут почти одинаковый алгоритм для обоих цпесов А, Б, нет двух разных алгоритмов для 2-х типов. - person justik; 22.01.2012
comment
Разве условие «М» не позволяет различать А и В? - person Martin Brandl; 22.01.2012
comment
@ jisaak: Я скомпилировал код, и компилятор обнаружил некоторые ошибки: Вместо isClassA ‹A› должна быть специализация для какого-то типа isClassA ‹A‹double› ›? Могу я попросить вас привести полный пример, в данный момент я получаю тот же код ошибки из своего вопроса... Спасибо. - person justik; 22.01.2012
comment
вы должны изменить вторую подпись для isClass на: template ‹ › struct isClassA‹ A ‹ double › › : public true_type{}; - person Martin Brandl; 22.01.2012

значение параметра m неизвестно до времени выполнения, поэтому компилятор должен генерировать код для ветвей if (m == ma) и else при специализации функции. Затем он жалуется, так как не может понять, что ему делать, если вы позвоните C::algo(a,mb) или тому подобное.

Как предложил Джон, перегрузка должна исправить ваш случай, попробуйте использовать этот код:

template<typename U>
static void f12(A<U>&u) { f1(u); }

template<typename U>
static void f12(B<U>&u) { f2(u); }

template<typename U>
static void algo(U& u, M m)
{
    /* long algorithm here
      ...
    */
    //use overloading to switch over U type instead of M value
    f12(u);
}

Также вы можете использовать указатели функций с функциями шаблона, если вы укажете параметры шаблона:

template<typename U>
static void algo(U& u, M m, void(*)(U&) func)
{
    /* ... */
    (*func)(u);
}

int main()
{
    A <double> a;
    C::algo (a, ma, &C::f1<double> );
}
person pqnet    schedule 22.01.2012
comment
@pqnet: Спасибо за помощь. Но я думал, что указатели на шаблоны функций не допускаются... - person justik; 22.01.2012