在C ++中实现“有限的通用性” [英] Achieving 'bounded genericity' in C++

查看:145
本文介绍了在C ++中实现“有限的通用性”的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我有一个类 X ,它用于处理 Y 类型的对象和从 Y 继承的对象。 X 经常需要从 Y 调用一个方法,比如 kewl_method(),并且此方法在从 Y 继承的每个类中都不相同。在Java中我可以做这样的事情:

  public class X< y extends Y> 

我会在中调用kewl_method() code> X 没有任何头痛,它会做我想要的。如果我理解正确(我是C ++的新手),那么在C ++中就不存在有限的泛型,所以如果我使用带有 X 的模板,就有可能完全填充任何东西,我将无法调用 kewl_method()的变体。



什么是在C ++中做到这一点的最佳方式?使用强制转换?



限制:我无法使用boost或TR1。

解决方案

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



但我想对您的问题发表评论:


所以如果我在X中使用模板,可以用任何东西来填充它

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



您必须记住, Java,有限的通用性不如限制泛型类所接受的类型,正如你似乎相信的那样,更多的是关于给泛型类更全面的​​泛型类型T的信息,以便能够在编译时验证对其方法的调用。



在C ++中,此功能按原样提供,并由e编译器:类似于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. * /}
};



正常C ++解决方案



,你可以提供你的模板类A:

  template< typename T> 
class A
{
public:
void foo()
{
T t;
t.kewl_method();
}
};

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




  • X:它实现 kewl_method()

  • Y:它公开从X实现,实现 kewl_method()

  • Z:它实现 kewl_method

  • K:它不实现 kewl_method()



...这比Java(或C#)的泛型功能强大得多。用户代码是:

pre $ int $ {
$
// A的约束是:implements kewl_method
A< X> X ; x.foo(); // OK:x实现kewl_method
A< Y> y; y.foo(); // OK:y从X
派生出来A< Z> z; z.foo(); // OK:z实现kewl_method
A< K> k; k.foo(); //不正确:K不会编译:/ main.cpp错误:
//'class K'没有名为'kewl_method'的成员
return 0;

$ / code>

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



如果约束真的需要,会怎样?



如果您想明确地将其限制为从X继承的类,则必须自己使用代码()直到 C ++概念是标准化的......他们错过了C ++ 0x的最后期限,所以我想我们将不得不等待下一个标准...



如果你真的想要约束,有多种方法。 虽然我知道这一点,但我对 SFINAE 概念给你解决方案,但我仍然可以看到两种方法来为你的情况应用约束(),当他们进行g ++ 4.4.5测试时,验证我的代码?):

添加一个未使用的转换?



B类相似添加一行代码给A类:

  template< typename T> //我们希望T派生自X 
class B
{
public:
void foo()
{
//创建一个未使用的变量,使用
//将它初始化到基类X中。如果T不是从
// X派生的,则在编译时cast会失败。
//在释放模式下,它可能会被优化掉
const X * x = static_cast< const T *>(NULL);

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

当B :: foo()被调用时,只有在 T * 可以转换为 X * (这只能通过公有继承来实现)。



结果是:

pre $ int $($)

// $ B的约束是:实现kewl_method,并且从X
B< X> X ; x.foo(); // OK:x是类型X
B< Y> y; y.foo(); //确定:y来自X
B< Z> z; z.foo(); //不行:z不会编译:main.cpp |错误:
//无法将'const Z *'转换为'const X *'
//在初始化过程中
B< K> k; k.foo(); //不行:K不会编译:/main.cpp错误:
//不能将'const K *'转换为'const X *'
//初始化
返回0 ;
}

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



添加本地约束 b

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

 模板< typename T,typename T_Base> ; 
class inheritance_constraint
{
public:
inheritance_constraint()
{
const T_Base * t = static_cast< const T *>(NULL);
}
};

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

您可以像下面这样使用它:

  template< typename T> 
class C:inheritance_constraint< T,X> //我们希望T从X
{
public:
void foo()
{
T t;
t.kewl_method();
}
};

私有继承意味着您的inheritance_constraint不会影响您的代码,但它仍然表示编译时间一个约束,它将停止不从X派生的类T的编译:



结果是:

  int main()
{
// C的约束条件是:实现了kewl_method,并且从X
派生得到C< X> X ; //确定:x的类型为X
C< Y> y; // OK:y从X
导出C< Z> z; //不正确:z不会编译:main.cpp错误:
//不能将'const Z *'转换为'const X *'
//初始化
C< K> ; k; //不行:K不会编译:/main.cpp错误:
//不能将'const K *'转换为'const X *'
//初始化
返回0 ;
}

问题在于它依赖于继承和构造函数调用才能生效。 / p>

用函数添加一个本地约束?



这个约束更像是一个静态断言,该方法被调用。首先,约束函数:

 模板< typename T,typename T_Base> 
void apply_inheritance_constraint()
{
//这段代码什么都不做,也没有副作用。它可能会在编译时被优化掉
//。
const T_Base * t = static_cast< const T *>(NULL);
};

然后这个类使用它:

 模板< typename T> 
class D
{
public:
void foo()
{
//在这里,我们将验证T是否继承自X
apply_inheritance_constraint< T,X>();

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

int main()
{
// D的约束是:实现kewl_method,并从X
D< X> X ; // OK:x是类型X
D< Y> y; //确定:y来自X
D< Z> z; //不正确:z不会编译:main.cpp错误:
//不能将'const Z *'转换为'const X *'
//在初始化中
D< K> ; k; //不正确:K不会编译:/main.cpp 2错误:
//'class K'没有名为'kewl_method'的成员
//不能将'const K *'转换为' const X *'
//初始化
return 0;
}

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



结论



您必须根据您的特定需求选择上述其中一种方法。



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

编辑2011-07-24



已添加另一部分的代码通过简单的函数调用来表达约束。

在添加未使用的投射?中部分,我替换了引用cast X& x = t; 与指针强制转换(如其他部分中所示),我相信这样会更好。

,指针转换最初是受现在删除的答案 Jonathan Grynspan


I'm transferring a project from Java to C++ and I have a problem with something relatively simple in Java.

I have a class X which is made to handle objects of type Y and objects inherited from Y. X often need to call a method from Y, say kewl_method(), and this method is different in each class inherited from Y. In Java I could do something like this:

public class X<y extends Y>

I would call kewl_method() in X without any headache and it would do what I want. If I understand correctly (I'm new to C++), there is no such thing as bounded genericity in C++, so if I use a template with X it would be possible to fill it with absolutely anything and I won't be able to call the variants of kewl_method().

What is the best way to do this in C++ ? Using casts ?

Restriction: I cannot use boost or TR1.

解决方案

TravisG (was: heishe) already answered, as far as I am concerned.

But I want to comment on your question:

so if I use a template with X it would be possible to fill it with absolutely anything

No, because it wouldn't compile without an accessible kewl_method.

You have to remember that in Java, bounded genericity is less about limiting the types accepted by your generic class as you seem to believe, and more about giving the generic class a more complete information on its generic type T to be able to validate the call to its methods at compile time.

In C++, this feature is provided as-is and where-used by the compiler: In a way similar to duck typing, but resolved at compile-time, the compiler will accept the compilation of the method only if the generic type class has access to the kewl_method.

For an example of 4 classes:

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. */ }
} ;

Normal C++ solution

With C++ templates, you can feed your templated class A:

template<typename T>
class A
{
    public :
        void foo()
        {
            T t ;
            t.kewl_method() ;
        }
} ;

... with the class X, Y and Z, but not K, because :

  • X : it implements kewl_method()
  • Y : it publicly derives from X, which implements kewl_method()
  • Z : it implements kewl_method()
  • K : it doesn't implements kewl_method()

... which is much more powerful than Java's (or C#'s) generics. The user code would be :

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

You need to call the foo() method to block the compilation.

What if constraints are really needed?

If you want to limit it explicitly to classes inheriting from X, you must do it yourself, using code (until the C++ concepts are standardized... They missed the C++0x deadline, so I guess we will have to wait for the next standard...)

If you really want to put constraints, there are multiple ways. While I know about it, I'm not familiar enough with the SFINAE concept to give you the solution, but I can still see two ways to apply constraints for your case (while they were tested for g++ 4.4.5, could someone wiser validate my code?):

Add a unused cast?

The B class is similar to the A class with one additional line of code:

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

Which, when B::foo() is called, would compile only if T* can be cast into X* (which is only be possible only through public inheritance).

The result would be :

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

But, as the A example, you need to call the foo() method to block the compilation.

Add a homegrown "constraint" with a class?

Let's create a class expressing a constraint on its constructor:

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

You'll note the class is empty, and its constructor does nothing, so chances are good it will be optimized away.

You would use it as in the following example:

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

The private inheritance means your "inheritance_constraint" would not screw with your code, but still, it expresses at compile time a constraint that would stop the compilation for a class T that don't derives from X:

The result would be :

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

The problem is that it relies on inheritance and constructor call to be effective.

Add a homegrown "constraint" with a function?

This constraint is more like a static assert, tested when the method is called. First, the constraint function:

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

Then the class using it:

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

But, as the A and B example, you need to call the foo() method to block the compilation.

Conclusion

You'll have to choose between one of the methods above, according to your specific needs.

But usually, as far as I am concerned, I find all this quite overkill, and I would use the first, simpler solution above.

Edit 2011-07-24

Added another section with the code to express the constraint through a simple function call.

In the "Add a unused cast?" section, I replaced the reference cast X & x = t ; with the pointer cast (as in the other sections), which I believe is better.

And to give Caesar its due, the pointer cast was originally inspired by a line of code in the now deleted answer of Jonathan Grynspan.

这篇关于在C ++中实现“有限的通用性”的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆