C++编译时多态性

C++ Compile-time polymorphism

本文关键字:多态性 编译 C++      更新时间:2023-10-16

有两个不相关的结构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。

我想根据M值用对象A或B运行静态方法algo。但是如何对我的编译器说:-)

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

但这个解决方案有一个大问题:两个实现都应该不必要地包含几乎相同的代码(为什么要写两次算法?)。

因此,我需要一个函数algor()来处理两种类型的参数A和B。有更舒适的解决方案吗?

您似乎在使用枚举来传递用户的类型信息。我建议你不要这样做。

在最简单的情况下,如果f1f2被重命名为f,那么你可以完全删除if并只调用它。编译器会为你调用适当的重载。

如果您不能或不想重命名函数模板,那么您可以编写一个助手模板来为您分派(未定义基类模板,分派到适当静态函数的AB的专业化)

如果枚举用于其他内容(编译器无法为您解析),您仍然可以传递它并重写要在枚举上调度的帮助程序,而不是参数的类型,并且您必须重写代码以使枚举值作为编译时常数(最简单的方法是:将其作为模板参数传递给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 );
}

同样,让它与一个类一起工作可能有点棘手,因为它可能需要几个正向声明,而且我手头没有编译器,但草图应该会引导你朝着正确的方向前进。

正常函数重载就足够了:

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

尽管目前还不清楚你会从这样的安排中得到什么。

如果你真的需要在一个函数中做到这一点,你可以使用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
    }
 };

m参数的值在运行时之前是未知的,因此编译器在专门化函数时必须为if (m == ma)else分支生成代码。然后它会抱怨,因为它不明白如果你碰巧打电话给C::algo(a,mb)或类似的人,他应该怎么做。

正如Jon所建议的,重载应该可以修复您的情况,请尝试使用以下代码:

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