在C++中实现"有界通用性"

PhD*_*hDP 7 c++ generics templates

我正在将一个项目从Java转移到C++,而我在Java中遇到一些相对简单的问题.

我有一个类X来处理Y从中继承的类型和对象的对象Y.X经常需要调用一个方法Y,比方说kewl_method(),这个方法在继承的每个类中都是不同的Y.在Java中,我可以这样做:

public class X<y extends Y>
Run Code Online (Sandbox Code Playgroud)

我所说的kewl_method()X没有任何头痛,它会做我想做的.如果我理解正确(我是C++的新手),在C++中没有有限的通用性,所以如果我使用模板X就可以用绝对的东西填充它,我将无法调用的变种kewl_method().

在C++中执行此操作的最佳方法是什么?使用演员?

限制:我不能使用boost或TR1.

pae*_*bal 15

就我而言,TravisG(曾是:heishe)已经回答了.

但我想评论你的问题:

因此,如果我使用带有X的模板,则可以填充绝对任何东西

不,因为如果没有可访问的话就无法编译kewl_method.

你必须记住,在Java中,有限的通用性不是像你想象的那样限制你的泛型类接受的类型,而是更多关于为泛型类提供关于其泛型类型T的更完整信息以便能够验证调用编译时的方法.

在C++中,此功能按原样提供并由编译器使用:在类似于duck typing的方式中,但在编译时解析,只有在泛型类具有访问权限时,编译器才会接受方法的编译到了kewl_method.

有关4个类的示例:

class X
{
    public : virtual void kewl_method() { /* etc. */ }
} ;

class Y : public X
{
    public : virtual void kewl_method() { /* etc. */ }
} ;

class Z
{
    public : virtual void kewl_method() { /* etc. */ }
} ;

class K
{
    public : virtual void wazaa() { /* etc. */ }
} ;
Run Code Online (Sandbox Code Playgroud)

普通的C++解决方案

使用C++模板,您可以提供模板化的类A:

template<typename T>
class A
{
    public :
        void foo()
        {
            T t ;
            t.kewl_method() ;
        }
} ;
Run Code Online (Sandbox Code Playgroud)

...与X,Y和Z类,但不是K,因为:

  • X:它实现了 kewl_method()
  • Y:它公开来自X,它实现了 kewl_method()
  • Z:它实现了 kewl_method()
  • K:它没有实现 kewl_method()

...它比Java(或C#)的泛型更强大.用户代码是:

int main()
{
    // A's constraint is : implements kewl_method
    A<X> x ; x.foo() ; // OK: x implements kewl_method
    A<Y> y ; y.foo() ; // OK: y derives from X
    A<Z> z ; z.foo() ; // OK: z implements kewl_method
    A<K> k ; k.foo() ; // NOT OK : K won't compile: /main.cpp error:
                       //   ‘class K’ has no member named ‘kewl_method’
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

您需要调用该foo()方法来阻止编译.

如果确实需要约束怎么办?

如果你想明确地将它限制为继承自X的类,你必须自己使用代码来做(直到C++概念被标准化......他们错过了C++ 0x截止日期,所以我想我们将不得不等待下一个标准...)

如果你真的想放置约束,有多种方法.虽然我知道这一点,但我对SFINAE概念不够熟悉,无法为您提供解决方案,但我仍然可以看到两种方法为您的案例应用约束(虽然它们经过了g ++ 4.4.5的测试,但有人可以更聪明地验证我的代码?):

添加未使用的演员?

B类类似于A类,另外还有一行代码:

template<typename T> // We want T to derive from X
class B
{
    public :
        void foo()
        {
            // creates an unused variable, initializing it with a
            // cast into the base class X. If T doesn't derive from
            // X, the cast will fail at compile time.
            // In release mode, it will probably be optimized away
            const X * x = static_cast<const T *>(NULL) ;

            T t ;
            t.kewl_method() ;
        }
} ;
Run Code Online (Sandbox Code Playgroud)

当调用B :: foo()时,只有在T*可以强制转换时才会编译X*(只有通过公共继承才能进行编译).

结果将是:

int main()
{
    // B's constraint is : implements kewl_method, and derives from X
    B<X> x ; x.foo() ; // OK : x is of type X
    B<Y> y ; y.foo() ; // OK : y derives from X
    B<Z> z ; z.foo() ; // NOT OK : z won't compile: main.cpp| error:
                       //      cannot convert ‘const Z*’ to ‘const X*’
                       //      in initialization
    B<K> k ; k.foo() ; // NOT OK : K won't compile: /main.cpp error:
                       //      cannot convert ‘const K*’ to ‘const X*’
                       //      in initialization
    return 0 ;
}
Run Code Online (Sandbox Code Playgroud)

但是,作为A示例,您需要调用该foo()方法来阻止编译.

用类添加本土"约束"?

让我们创建一个在其构造函数上表达约束的类:

template<typename T, typename T_Base>
class inheritance_constraint
{
    public:
        inheritance_constraint()
        {
            const T_Base * t = static_cast<const T *>(NULL) ;
        }
} ;
Run Code Online (Sandbox Code Playgroud)

你会注意到这个类是空的,它的构造函数什么都不做,所以很有可能它会被优化掉.

您将使用它,如以下示例所示:

template<typename T>
class C : inheritance_constraint<T, X> // we want T to derive from X
{
    public :
        void foo()
        {
            T t ;
            t.kewl_method() ;
        }
} ;
Run Code Online (Sandbox Code Playgroud)

私有继承意味着你的"inheritance_constraint"不会搞砸你的代码,但它仍然在编译时表达一个约束,该约束将停止不从X派生的类T的编译:

结果将是:

int main()
{
    // C's constraint is : implements kewl_method, and derives from X
    C<X> x ; // OK : x is of type X
    C<Y> y ; // OK : y derives from X
    C<Z> z ; // NOT OK : z won't compile: main.cpp error:
             //      cannot convert ‘const Z*’ to ‘const X*’
             //      in initialization
    C<K> k ; // NOT OK : K won't compile: /main.cpp error:
             //      cannot convert ‘const K*’ to ‘const X*’
             //      in initialization
    return 0 ;
}
Run Code Online (Sandbox Code Playgroud)

问题是它依赖于继承和构造函数调用才有效.

添加一个功能的本土"约束"?

此约束更像是静态断言,在调用方法时进行测试.一,约束功能:

template<typename T, typename T_Base>
void apply_inheritance_constraint()
{
    // This code does nothing, and has no side effects. It will probably
    // be optimized away at compile time.
    const T_Base * t = static_cast<const T *>(NULL) ;
} ;
Run Code Online (Sandbox Code Playgroud)

然后使用它的类:

template<typename T>
class D
{
    public :
        void foo()
        {
            // Here, we'll verify if T  inherits from X
            apply_inheritance_constraint<T, X>() ;

            T t ;
            t.kewl_method() ;
        }
} ;

int main()
{
    // D's constraint is : implements kewl_method, and derives from X
    D<X> x ; // OK : x is of type X
    D<Y> y ; // OK : y derives from X
    D<Z> z ; // NOT OK : z won't compile: main.cpp error:
             //      cannot convert ‘const Z*’ to ‘const X*’
             //      in initialization
    D<K> k ; // NOT OK : K won't compile: /main.cpp 2 errors:
             //      ‘class K’ has no member named ‘kewl_method’
             //      cannot convert ‘const K*’ to ‘const X*’
             //      in initialization
    return 0 ;
}
Run Code Online (Sandbox Code Playgroud)

但是,作为A和B示例,您需要调用该foo()方法来阻止编译.

结论

您必须根据您的具体需求在上述方法之一中进行选择.

但通常,就我而言,我发现所有这些都太过分了,我会使用上面第一个更简单的解决方案.

编辑2011-07-24

添加了另一个带有代码的部分,通过简单的函数调用来表达约束.

在"添加未使用的演员阵容?" 部分,我用X & x = t ;指针转换替换了引用转换(如在其他部分中),我认为这更好.

为了给凯撒带来应有的价值,指针演员最初的灵感来自Jonathan Grynspan 现在删除的答案中的一行代码.