调用派生类的模板函数

发布于 2024-11-08 01:16:25 字数 913 浏览 0 评论 0原文

我在 C++ 中遇到问题,在拥有指向基类的指针的同时调用派生类的函数。

编辑: 一些答案让我参考 CRTP

但我的观点是我需要一个指向“ Base*”类而不是“Base*”,因为我不知道当前正在处理的类型(当前实例是从某种工厂创建的)。

类:

class Base 
{
..
template<typename T>
func (T arg) { ... };
};

class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};


class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};

用法:

int main()
{
   Base* BasePtr = new Derived1();

   // The expected function to be called is Derived1::func<int>()
   BasePtr->func<int>();

   return 0; // :)
}

我无法将 func 设为虚拟,因为该语言不支持虚拟模板函数。

仅当只有类具有模板参数时才允许,但如果类中的函数具有模板参数则不允许。

我在 Boost.Serialization 中看到类似的问题得到解决,但无法理解解决方案。

谢谢,

科比·梅尔

I'm having a problem in C++ with calling a function of a derived class while having a pointer to the base class.

EDIT:
Some answers referred me to CRTP

but my point is that I need to have a pointer to the "Base*" class not "Base*" because I'm unaware of the type currently being handled (The current instance is created from some sort of a factory).

Classes:

class Base 
{
..
template<typename T>
func (T arg) { ... };
};

class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};


class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};

Usage:

int main()
{
   Base* BasePtr = new Derived1();

   // The expected function to be called is Derived1::func<int>()
   BasePtr->func<int>();

   return 0; // :)
}

I can't make func virtual because the language does not support virtual template function.

It is only allowed if only the class is have template arguments but not if the function within it has template arguments.

I have seen a similar problem resolved within Boost.Serialization but couldn't understand the solution.

Thanks,

Koby Meir

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我的奇迹 2024-11-15 01:16:25

实施奇怪的重复模板模式 (CTRP)

插图:

template<typename D>
class Base 
{
public:
    template<typename T>
    void func (T arg) 
    {
         static_cast<D*>(this)->func(arg);
    }
};

class Derived1 : public Base<Derived1>
{
public:
    template<typename T>
    void func (T arg) { /*...*/ }
};


Base<Derived1> *basePtr = new Base<Derived1>();
basePtr->func(100);

Implement Curiously recurring template pattern(CTRP).

Illustration:

template<typename D>
class Base 
{
public:
    template<typename T>
    void func (T arg) 
    {
         static_cast<D*>(this)->func(arg);
    }
};

class Derived1 : public Base<Derived1>
{
public:
    template<typename T>
    void func (T arg) { /*...*/ }
};


Base<Derived1> *basePtr = new Base<Derived1>();
basePtr->func(100);
云雾 2024-11-15 01:16:25

现有的两种解决方案将动态多态性换成了静态多态性。如果没有关于当前问题的更多详细信息,就不可能知道这是否是一种有效的方法,因为它基本上打破了多态层次结构:对于 CRTP 来说,没有单一的基类,而是一个基类。您不能将 Derived1Derived2 的对象放在同一个容器中,因为它们不相关...如果您只需要共享代码,但不共享代码,那么这是一个很好的解决方案如果你需要动态多态性。查看访客模式和双重调度以解决类似问题。

如果您需要动态多态性,您可以尝试实现双重分派(这很痛苦,但如果层次结构足够小,则可行。基本上创建两个不同的层次结构,一个以Base为根,另一个作为缺少手动调度程序。以 Base 为根的层次结构将具有一个虚拟方法 apply,第二个层次结构将为第一个层次结构中的每个类型提供虚拟函数。 :

class Base;
class Derived1;  // inherits from Base, implements Visitor
class Derived2;  // inherits from either Base or Derived2
struct Visitor {
   virtual void visit( Base& ) = 0;     // manually unrolled for all types
   virtual void visit( Derived1& ) = 0;
   virtual void visit( Derived2& ) = 0;
};
struct Base {
   virtual void apply( Visitor& v ) {   // manually replicate this in Derived1, 2
      v.visit( *this );
   }
   template <typename T> void foo(T);   // implement 
};

template <typename T>
struct FooCaller : Visitor {
    T& ref_value;
    FooCaller( T& v ) : ref_value(v) {}
    template <typename U> void call_foo( U& o ) {
       o.foo(ref_value);
    }
    virtual void visit( Base & b )      { call_foo(b); }
    virtual void visit( Derived1 & d1 ) { call_foo(d1); }
    virtual void visit( Derived2 & d2 ) { call_foo(d2); } 
};

我使用的名称在Visitor模式中很常见,这种方法与该模式非常相似(我不敢称其为Visitor模式,但方法类似,所以我只是借用了

User的 命名约定)。代码将类似于:

int main()                     // main returns int, not void!!!
{
   Base* BasePtr = new Derived1();
   int i = 5;
   FooCaller<int> c(i)
   BasePtr->apply(c);          // [1] magic happens here
}

通过将函数的参数从引用更改为 const 引用(如果可能),可以释放事先声明 ic 的要求。真正的魔力在于,在 [1] 中,C++ 单一调度机制仍将调用调度到 Derived1::apply,因为这是 BasePtr 指向的对象的动态类型。此时它将以自身作为参数调用 Visitor::visit( Derived1& ) 。它将再次通过单一调度机制调度到 FooCaller::visit( Derived1& ),此时两个对象都已解析为其静态类型。当 FooCaller::visit 调用 call_foo 时,参数 U 被推导为 Derived1,当它调用时Derived1::foo 参数被推断为 int 并且最终调用 Derived1::foo...尽管有几个循环和间接...

根据您的特定用例,这可能太复杂(如果像 CRTP 这样的静态多态性可以工作)或太难维护(如果层次结构很大:对于 Base< 中的每个新元素/code> 层次结构,您必须更新 Visitor 层次结构中的所有类型),因此,如果您可以避免这种复杂性,那就完美了。但在某些情况下,您需要这个。

另请注意,这是最复杂的完全动态解决方案,两者之间还有其他选项,具体取决于您需要运行时多态性......可能是这样的情况,您的层次结构模型是短裤的访问者,并且您只需要手动展开将在内部分派到模板的不同虚拟函数,在这种情况下,上述复杂性将消失一半。

另请注意,这在 C++ 中很不寻常,如果您解释手头的实际问题,可能会有更好的更简单的解决方案,您所说的是原始问题的解决方案的要求:动态分派到模板。

The two existing solutions trade dynamic polymorphism for static polymorphism. Without more details on the problem at hand, it is not possible to know whether that is a valid approach or not, as it basically breaks the polymorphic hierarchy: with CRTP there is no single base class, but rather a family of them. You cannot hold objects of Derived1 and Derived2 in the same container as they are unrelated... It is a fine solution if all you need is to share the code, but not if you need dynamic polymorphism. Take a look at the Visitor pattern and at double-dispatch for similar problems.

If you need dynamic polymorphism, you could try to implement double dispatch (it is a pain, but feasible if the hierarchy is small enough. Basically create two different hierarchies, one rooted at Base and another that serves as some short of manual dispatcher. The hierarchy rooted at Base will have a virtual method apply, and the second hierarchy will have virtual functions for each one of the types in the first hierarchy:

class Base;
class Derived1;  // inherits from Base, implements Visitor
class Derived2;  // inherits from either Base or Derived2
struct Visitor {
   virtual void visit( Base& ) = 0;     // manually unrolled for all types
   virtual void visit( Derived1& ) = 0;
   virtual void visit( Derived2& ) = 0;
};
struct Base {
   virtual void apply( Visitor& v ) {   // manually replicate this in Derived1, 2
      v.visit( *this );
   }
   template <typename T> void foo(T);   // implement 
};

template <typename T>
struct FooCaller : Visitor {
    T& ref_value;
    FooCaller( T& v ) : ref_value(v) {}
    template <typename U> void call_foo( U& o ) {
       o.foo(ref_value);
    }
    virtual void visit( Base & b )      { call_foo(b); }
    virtual void visit( Derived1 & d1 ) { call_foo(d1); }
    virtual void visit( Derived2 & d2 ) { call_foo(d2); } 
};

The names I have used are common in the Visitor pattern, and this approach is quite similar to that pattern (I don't dare call it the Visitor pattern, but the approach is similar, so I just borrowed the naming convention).

User code would be similar to:

int main()                     // main returns int, not void!!!
{
   Base* BasePtr = new Derived1();
   int i = 5;
   FooCaller<int> c(i)
   BasePtr->apply(c);          // [1] magic happens here
}

The requirement of declaring i and c before hand can be released by changing (if possible) the arguments to the functions from references to const-references. The actual magic is that in [1] the C++ single dispatch mechanism sill dispatch the call to Derived1::apply, since that is the dynamic type of the object pointed by BasePtr. At that point it will call Visitor::visit( Derived1& ) with itself as the argument. That will again be dispatched through the single dispatch mechanism to FooCaller<int>::visit( Derived1& ), and at that point both objects have been resolved to their static types. When FooCaller<int>::visit calls call_foo the argument U is deduced to be Derived1, when it calls Derived1::foo the argument is deduced to be int and it ends up calling Derived1::foo<int>... though a couple of loops and indirections...

Depending on your particular use case this might be too complex (if static polymorphism like CRTP would work) or too hard to maintain (if the hierarchy is big: for each new element in the Base hierarchy you will have to update all types in the hierarchy of the Visitor), so if you can avoid this complexity, perfect. In some cases, though, you need this.

Also note that this is the most complex fully dynamic solution, there are other options in between, depending on what is it that you need to be runtime polymorphism... It might be the case that your hierarchy models a visitor of shorts, and that you only need to manually unroll the different virtual functions that will dispatch to the template internally, in which case half of the above complexity will be gone.

Also note that this is quite unusual in C++, and that if you explain the actual problem at hand, there might be better simpler solutions, what you have stated are the requirements of your solution to the original problem: dynamically dispatch to a template.

别忘他 2024-11-15 01:16:25

检查这个,它将帮助您实现 CRTP。

Check this out, it will help you implement the CRTP.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文