【问题标题】:Can a class member function template be virtual?类成员函数模板可以是虚拟的吗?
【发布时间】:2011-01-22 04:34:39
【问题描述】:

我听说 C++ 类成员函数模板不能是虚拟的。这是真的?

如果它们可以是虚拟的,那么使用这种功能的场景示例是什么?

【问题讨论】:

  • 我也遇到过类似的问题,也了解到同时做虚拟和模板是有争议的。我的解决方案是编写在派生类中通用的模板魔法,并调用一个纯虚函数来完成专门的部分。这当然与我的问题的性质有关,因此可能并非在所有情况下都有效。

标签: c++ templates virtual-functions function-templates c++-faq


【解决方案1】:

不,模板成员函数不能是虚函数。

【讨论】:

  • 我的好奇心是:为什么?编译器这样做会面临哪些问题?
  • 您需要在范围内声明(至少,为了使类型正确)。标准(和语言)要求在您使用的标识符的范围内声明。
【解决方案2】:

模板都是关于编译器在编译时生成代码的。虚函数都是关于运行时系统确定在运行时调用哪个函数。

一旦运行时系统发现它需要调用模板化的虚函数,编译就完成了,编译器无法再生成适当的实例。因此,您不能拥有虚拟成员函数模板。

但是,结合多态性和模板,产生了一些强大而有趣的技术,特别是所谓的 type erasure

【讨论】:

  • 我没有看到 语言 原因,只有 实施 原因。 vtables 不是语言的一部分——只是编译器实现语言的标准方式。
  • Virtual functions are all about the run-time system figuring out which function to call at run-time - 抱歉,这是一种相当错误的方法,而且相当混乱。它只是间接的,并且不涉及“运行时计算”,在编译时就知道要调用的函数是 vtable 中第 n 个指针所指向的函数。 “弄清楚”意味着有类型检查等,但事实并非如此。 Once the run-time system figured out it would need to call a templatized virtual function - 函数是否为虚拟函数在编译时就知道了。
  • @ddriver: 1. 如果编译器看到void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); },那么它“知道”在调用cb.f() 时调用了哪个函数,并且不知道为vb.f()。后者必须在运行时由运行时系统发现。无论您是否想将其称为“弄清楚”,以及这或多或少的效率,都不会改变这些事实。
  • @ddriver: 2. (成员)函数模板的实例是(成员)函数,因此将指向此类实例的指针放入 vtable 完全没有问题.但是需要哪些模板实例只有在编译调用者时才知道,而 vtables 是在编译基类和派生类时设置的。而这些都是单独编译的。更糟糕的是——新的派生类可以在运行时链接到正在运行的系统(想想你的浏览器动态加载插件)。创建新的派生类时,甚至调用者的源代码也可能早已丢失。
  • @sbi:你为什么要根据我的名字做出假设?我没有混淆泛型和模板。我知道 Java 的泛型纯粹是运行时的。您没有详尽地解释为什么不能在 C++ 中使用虚拟成员函数模板,但 InQsitive 做到了。您将模板和虚拟机制过度简化为“编译时”与“运行时”,并得出结论“您不能拥有虚拟成员函数模板”。我引用了 InQsitive 的答案,其中引用了“C++ 模板完整指南”。我不认为这是“挥手”。祝你有美好的一天。
【解决方案3】:

C++ 目前不允许虚拟模板成员函数。最可能的原因是实现它的复杂性。 Rajendra 给出了现在不能完成的充分理由,但可以通过对标准进行合理的更改来实现。如果考虑到虚函数调用的位置,特别是要计算出实际存在多少模板化函数的实例并构建 vtable 似乎很困难。标准人员现在还有很多其他事情要做,而 C++1x 对编译器编写者来说也是很多工作。

什么时候需要模板化成员函数?我曾经遇到过这样一种情况,我试图用纯虚拟基类重构层次结构。对于实施不同的策略,这是一种糟糕的风格。我想将其中一个虚拟函数的参数更改为数字类型,而不是重载成员函数并覆盖所有子类中的每个重载,我尝试使用虚拟模板函数(并且不得不发现它们不存在.)

【讨论】:

  • @pmr:一个虚函数可能会从编译函数时甚至不存在的代码中调用。编译器如何确定为甚至不存在的代码生成(理论上的)虚拟模板成员函数的哪些实例?
  • @sbi:是的,单独编译会是个大问题。我根本不是 C++ 编译器方面的专家,所以我无法提供解决方案。与一般的模板化函数一样,它应该在每个编译单元中再次实例化,对吗?这不是解决问题吗?
  • @sbi 如果您指的是动态加载库,这是模板类/函数的普遍问题,而不仅仅是虚拟模板方法。
  • "C++ 不允许 [...]" - 希望看到对标准的引用(无论在编写答案时是否是最新的或八年后最新的)...
  • 一种可能的解决方案是启用某种稳定的运行时类型反射,然后创建 (type, function-ptr) 的哈希映射而不是 vtable。这是可行的。但非常复杂,与我们现在所拥有的非常不同。
【解决方案4】:

以下代码可以在Window 7上使用MinGW G++ 3.4.5编译并正常运行:

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class A{
public:
    virtual void func1(const T& p)
    {
        cout<<"A:"<<p<<endl;
    }
};

template <typename T>
class B
: public A<T>
{
public:
    virtual void func1(const T& p)
    {
        cout<<"A<--B:"<<p<<endl;
    }
};

int main(int argc, char** argv)
{
    A<string> a;
    B<int> b;
    B<string> c;

    A<string>* p = &a;
    p->func1("A<string> a");
    p = dynamic_cast<A<string>*>(&c);
    p->func1("B<string> c");
    B<int>* q = &b;
    q->func1(3);
}

输出是:

A:A<string> a
A<--B:B<string> c
A<--B:3

后来我添加了一个新的类X:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

当我尝试像这样在 main() 中使用 X 类时:

X x;
x.func2<string>("X x");

g++报如下错误:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

所以很明显:

  • 虚拟成员函数可以在类模板中使用。编译器很容易构造vtable
  • 不可能将类模板成员函数定义为虚函数,如您所见,很难确定函数签名和分配 vtable 条目。

【讨论】:

  • 一个类模板可能有虚成员函数。成员函数不能既是成员函数模板又是虚拟成员函数。
  • 它实际上在 gcc 4.4.3 上失败了。在我的系统上肯定是 Ubuntu 10.04
  • 这与问题完全不同。这里整个基类都是模板化的。我以前编译过这种东西。这也可以在 Visual Studio 2010 上编译
【解决方案5】:

回答问题的第二部分:

如果它们可以是虚拟的,那么使用这种功能的场景示例是什么?

这不是一件不合理的事情。例如,Java(其中每个方法都是虚拟的)对于泛型方法没有问题。

C++ 中需要虚函数模板的一个例子是接受泛型迭代器的成员函数。或者接受通用函数对象的成员函数。

解决此问题的方法是使用带有 boost::any_range 和 boost::function 的类型擦除,这将允许您接受通用迭代器或仿函数,而无需将您的函数设为模板。

【讨论】:

  • Java 泛型是用于转换的语法糖。它们与模板不同。
  • @BriceM.Dempsey:你可以说强制转换是 Java 实现泛型的方式,而不是相反的方式......从语义上讲,用例 exclipy 呈现是有效的 IMO。
【解决方案6】:

虚函数表

让我们先了解一下虚函数表的背景知识以及它们的工作原理 (source):

[20.3] 虚拟和非虚拟有什么区别 成员函数被调用了吗?

非虚拟成员函数是静态解析的。那就是 成员函数是基于静态选择的(在编译时) 指向对象的指针(或引用)的类型。

相比之下,虚成员函数是动态解析的(在 运行)。也就是说,成员函数是动态选择的(在 运行时)基于对象的类型,而不是对象的类型 指向该对象的指针/引用。这称为“动态绑定”。 大多数编译器使用以下技术的一些变体:如果 对象有一个或多个虚函数,编译器把一个隐藏的 对象中的指针称为“virtual-pointer”或“v-pointer”。这 v-pointer 指向一个名为“virtual-table”的全局表或 “v-table”。

编译器为每个至少有一个的类创建一个 v-table 虚函数。例如,如果类 Circle 具有虚函数 对于 draw() 和 move() 和 resize(),只有一个 v-table 与类 Circle 相关联,即使有一个 gazillion Circle 对象,并且每个 Circle 对象的 v 指针将指向 到 Circle v 表。 v-table 本身有指向每个 类中的虚函数。例如,Circle v-table 将 有三个指针:一个指向 Circle::draw() 的指针,一个指向 Circle::move(),以及指向 Circle::resize() 的指针。

在一个虚函数的调度过程中,运行时系统遵循 对象的 v 指针指向类的 v 表,然后跟随 v-table 中的适当槽位到方法代码。

上述技术的空间成本开销是名义上的:额外的 每个对象的指针(但仅适用于需要动态处理的对象 绑定),每个方法加上一个额外的指针(但仅适用于虚拟 方法)。时间成本开销也相当小:与 普通函数调用,一个虚函数调用需要两个额外的 fetches(一个获取 v-pointer 的值,第二个获取 方法的地址)。此运行时活动均未发生 非虚函数,因为编译器解析非虚函数 仅在编译时基于类型的函数 指针。


我的问题,或者我是怎么来到这里的

我现在正尝试将类似的东西用于具有模板化优化加载函数的立方体文件基类,这些函数将针对不同类型的立方体(一些按像素存储,一些按图像存储等)以不同方式实现。

一些代码:

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

我希望它是什么,但由于虚拟模板组合而无法编译:

template<class T>
    virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

我最终将模板声明移至类级别。这种解决方案会强制程序在读取数据之前了解它们将读取的特定类型的数据,这是不可接受的。

解决方案

警告,这不是很漂亮,但它允许我删除重复的执行代码

1) 在基类中

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

2) 在子类中

void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

template<class T>
void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);

请注意,LoadAnyCube 未在基类中声明。


这是另一个解决方法的堆栈溢出答案: need a virtual template member workaround

【讨论】:

  • 我也遇到过同样的情况,还有海量类的继承结构。宏有帮助。
【解决方案7】:

来自 C++ 模板的完整指南:

成员函数模板不能声明为虚拟的。这个约束 强加是因为虚函数的通常实现 调用机制使用一个固定大小的表,每个虚拟一个条目 功能。但是,成员函数的实例化次数 模板在整个程序被翻译之前是不固定的。 因此,支持虚拟成员函数模板需要 在 C++ 编译器中支持一种全新的机制 链接器。相比之下,类模板的普通成员可以 虚拟的,因为它们的数量在类被实例化时是固定的

【讨论】:

  • 我认为当今的 C++ 编译器和链接器,尤其是具有链接时间优化支持的,应该能够在链接时生成所需的 vtable 和偏移量。那么也许我们会在 C++2b 中得到这个特性?
  • 我认为它在很长一段时间内都行不通。请记住,带有模板虚函数的接口类不仅可以在您自己的代码中使用,还可以包含在多个“客户端”二进制文件中,可能编译为动态链接的共享库。现在,假设这些库中的每一个都从您的类继承并引入了一个新的函数实例。想象一下,您动态打开这些共享库,例如通过dlopendlopen 发生时的链接过程会很麻烦,可能需要为已经在内存中的对象重新创建 vtables!
【解决方案8】:

不,他们不能。但是:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

如果您只想拥有一个通用接口并将实现推迟到子类,则效果大致相同。

【讨论】:

  • 如果有人好奇,这就是所谓的 CRTP。
  • 但这对于具有类层次结构并希望能够调用指向基类的指针的虚拟方法的情况没有帮助。您的Foo 指针被限定为Foo&lt;Bar&gt;,它不能指向Foo&lt;Barf&gt;Foo&lt;XXX&gt;
  • @KaiPetzke:你不能构造一个不受约束的指针,不。但是您可以模板化任何不需要知道具体类型的代码,这具有几乎相同的效果(至少在概念上 - 显然完全不同的实现)。
【解决方案9】:

至少在 gcc 5.4 中,虚函数可以是模板成员,但本身必须是模板。

#include <iostream>
#include <string>
class first {
protected:
    virtual std::string  a1() { return "a1"; }
    virtual std::string  mixt() { return a1(); }
};

class last {
protected:
    virtual std::string a2() { return "a2"; }
};

template<class T>  class mix: first , T {
    public:
    virtual std::string mixt() override;
};

template<class T> std::string mix<T>::mixt() {
   return a1()+" before "+T::a2();
}

class mix2: public mix<last>  {
    virtual std::string a1() override { return "mix"; }
};

int main() {
    std::cout << mix2().mixt();
    return 0;
}

输出

mix before a2
Process finished with exit code 0

【讨论】:

    【解决方案10】:

    如果事先知道模板方法的类型集,则有一个“虚拟模板方法”的解决方法。

    为了展示这个想法,在下面的示例中仅使用了两种类型(intdouble)。

    在那里,一个“虚拟”模板方法 (Base::Method) 调用相应的虚拟方法(Base::VMethod 之一),而后者又调用模板方法实现 (Impl::TMethod)。

    只需要在派生实现(AImplBImpl)中实现模板方法TMethod并使用Derived&lt;*Impl&gt;

    class Base
    {
    public:
        virtual ~Base()
        {
        }
    
        template <typename T>
        T Method(T t)
        {
            return VMethod(t);
        }
    
    private:
        virtual int VMethod(int t) = 0;
        virtual double VMethod(double t) = 0;
    };
    
    template <class Impl>
    class Derived : public Impl
    {
    public:
        template <class... TArgs>
        Derived(TArgs&&... args)
            : Impl(std::forward<TArgs>(args)...)
        {
        }
    
    private:
        int VMethod(int t) final
        {
            return Impl::TMethod(t);
        }
    
        double VMethod(double t) final
        {
            return Impl::TMethod(t);
        }
    };
    
    class AImpl : public Base
    {
    protected:
        AImpl(int p)
            : i(p)
        {
        }
    
        template <typename T>
        T TMethod(T t)
        {
            return t - i;
        }
    
    private:
        int i;
    };
    
    using A = Derived<AImpl>;
    
    class BImpl : public Base
    {
    protected:
        BImpl(int p)
            : i(p)
        {
        }
    
        template <typename T>
        T TMethod(T t)
        {
            return t + i;
        }
    
    private:
        int i;
    };
    
    using B = Derived<BImpl>;
    
    int main(int argc, const char* argv[])
    {
        A a(1);
        B b(1);
        Base* base = nullptr;
    
        base = &a;
        std::cout << base->Method(1) << std::endl;
        std::cout << base->Method(2.0) << std::endl;
    
        base = &b;
        std::cout << base->Method(1) << std::endl;
        std::cout << base->Method(2.0) << std::endl;
    }
    

    输出:

    0
    1
    2
    3
    

    注意: Base::Method 实际代码是多余的(VMethod 可以公开直接使用)。 我添加了它,使它看起来像一个实际的“虚拟”模板方法。

    【讨论】:

    • 我在解决工作中的问题时想出了这个解决方案。它似乎与上述 Mark Essel 的类似,但我希望能够更好地实现和解释。
    • 我已经将此定义为代码混淆,但您仍然无法避免每次需要调用模板函数时都必须修改原始 Base 类参数类型与目前实现的不兼容。避免这种必要性是模板的意图......
    • Essels 方法完全不同:普通虚函数接受不同的模板实例化——派生类中的最终模板函数仅用于避免代码重复,甚至在基类中没有对应部分。 ..
    【解决方案11】:

    在其他答案中,建议的模板功能是一种外观,并没有提供任何实际好处。

    • 模板函数仅用于编写代码一次,使用 不同种类。
    • 虚拟函数对于为不同的类提供通用接口很有用。

    该语言不允许使用虚拟模板功能,但有一种解决方法可以同时拥有这两种功能,例如每个类一个模板实现和一个虚拟通用接口。

    然而,有必要为每个模板类型组合定义一个虚拟包装函数:

    #include <memory>
    #include <iostream>
    #include <iomanip>
    
    //---------------------------------------------
    // Abstract class with virtual functions
    class Geometry {
    public:
        virtual void getArea(float &area) = 0;
        virtual void getArea(long double &area) = 0;
    };
    
    //---------------------------------------------
    // Square
    class Square : public Geometry {
    public:
        float size {1};
    
        // virtual wrapper functions call template function for square
        virtual void getArea(float &area) { getAreaT(area); }
        virtual void getArea(long double &area) { getAreaT(area); }
    
    private:
        // Template function for squares
        template <typename T>
        void getAreaT(T &area) {
            area = static_cast<T>(size * size);
        }
    };
    
    //---------------------------------------------
    // Circle
    class Circle : public Geometry  {
    public:
        float radius {1};
    
        // virtual wrapper functions call template function for circle
        virtual void getArea(float &area) { getAreaT(area); }
        virtual void getArea(long double &area) { getAreaT(area); }
    
    private:
        // Template function for Circles
        template <typename T>
        void getAreaT(T &area) {
            area = static_cast<T>(radius * radius * 3.1415926535897932385L);
        }
    };
    
    
    //---------------------------------------------
    // Main
    int main()
    {
        // get area of square using template based function T=float
        std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
        float areaSquare;
        geometry->getArea(areaSquare);
    
        // get area of circle using template based function T=long double
        geometry = std::make_unique<Circle>();
        long double areaCircle;
        geometry->getArea(areaCircle);
    
        std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
        return 0;
    }
    

    输出:

    正方形面积为1,圆形面积为3.1415926535897932385

    试试here

    【讨论】:

      【解决方案12】:

      虽然许多人已经回答了一个较老的问题,但我相信一个简洁的方法,与其他发布的方法没有太大不同,是使用一个次要宏来帮助简化类声明的重复。

      // abstract.h
      
      // Simply define the types that each concrete class will use
      #define IMPL_RENDER() \
          void render(int a, char *b) override { render_internal<char>(a, b); }   \
          void render(int a, short *b) override { render_internal<short>(a, b); } \
          // ...
      
      class Renderable
      {
      public:
          // Then, once for each on the abstract
          virtual void render(int a, char *a) = 0;
          virtual void render(int a, short *b) = 0;
          // ...
      };
      

      那么现在,来实现我们的子类:

      class Box : public Renderable
      {
      public:
          IMPL_RENDER() // Builds the functions we want
      
      private:
          template<typename T>
          void render_internal(int a, T *b); // One spot for our logic
      };
      

      这里的好处是,当添加一个新支持的类型时,这一切都可以从抽象头文件中完成,并放弃可能在多个源/头文件中纠正它。

      【讨论】:

      • 如何调用“IMPL_RENDER() // 构建我们想要的函数”? @mccatnm
      • 纯粹是一个宏。对于此示例,您可以在宏定义中排除 ()。它并不意味着被调用,而是通过预编译器填写所需的函数。否则,您将不得不重新定义所有功能。 (例如Box::render(int, char *)Box::render(int, short *) 等)
      【解决方案13】:

      我当前的解决方案如下(禁用 RTTI - 您也可以使用 std::type_index):

      #include <type_traits>
      #include <iostream>
      #include <tuple>
      
      class Type
      {
      };
      
      template<typename T>
      class TypeImpl : public Type
      {
      
      };
      
      template<typename T>
      inline Type* typeOf() {
          static Type* typePtr = new TypeImpl<T>();
          return typePtr;
      }
      
      /* ------------- */
      
      template<
          typename Calling
          , typename Result = void
          , typename From
          , typename Action
      >
      inline Result DoComplexDispatch(From* from, Action&& action);
      
      template<typename Cls>
      class ChildClasses
      {
      public:
          using type = std::tuple<>;
      };
      
      template<typename... Childs>
      class ChildClassesHelper
      {
      public:
          using type = std::tuple<Childs...>;
      };
      
      //--------------------------
      
      class A;
      class B;
      class C;
      class D;
      
      template<>
      class ChildClasses<A> : public ChildClassesHelper<B, C, D> {};
      
      template<>
      class ChildClasses<B> : public ChildClassesHelper<C, D> {};
      
      template<>
      class ChildClasses<C> : public ChildClassesHelper<D> {};
      
      //-------------------------------------------
      
      class A
      {
      public:
          virtual Type* GetType()
          {
              return typeOf<A>();
          }
      
          template<
              typename T,
              bool checkType = true
          >
              /*virtual*/void DoVirtualGeneric()
          {
              if constexpr (checkType)
              {
                  return DoComplexDispatch<A>(this, [&](auto* other) -> decltype(auto)
                      {
                          return other->template DoVirtualGeneric<T, false>();
                      });
              }
              std::cout << "A";
          }
      };
      
      class B : public A
      {
      public:
          virtual Type* GetType()
          {
              return typeOf<B>();
          }
          template<
              typename T,
              bool checkType = true
          >
          /*virtual*/void DoVirtualGeneric() /*override*/
          {
              if constexpr (checkType)
              {
                  return DoComplexDispatch<B>(this, [&](auto* other) -> decltype(auto)
                      {
                          other->template DoVirtualGeneric<T, false>();
                      });
              }
              std::cout << "B";
          }
      };
      
      class C : public B
      {
      public:
          virtual Type* GetType() {
              return typeOf<C>();
          }
      
          template<
              typename T,
              bool checkType = true
          >
          /*virtual*/void DoVirtualGeneric() /*override*/
          {
              if constexpr (checkType)
              {
                  return DoComplexDispatch<C>(this, [&](auto* other) -> decltype(auto)
                      {
                          other->template DoVirtualGeneric<T, false>();
                      });
              }
              std::cout << "C";
          }
      };
      
      class D : public C
      {
      public:
          virtual Type* GetType() {
              return typeOf<D>();
          }
      };
      
      int main()
      {
          A* a = new A();
          a->DoVirtualGeneric<int>();
      }
      
      // --------------------------
      
      template<typename Tuple>
      class RestTuple {};
      
      template<
          template<typename...> typename Tuple,
          typename First,
          typename... Rest
      >
      class RestTuple<Tuple<First, Rest...>> {
      public:
          using type = Tuple<Rest...>;
      };
      
      // -------------
      template<
          typename CandidatesTuple
          , typename Result
          , typename From
          , typename Action
      >
      inline constexpr Result DoComplexDispatchInternal(From* from, Action&& action, Type* fromType)
      {
          using FirstCandidate = std::tuple_element_t<0, CandidatesTuple>;
      
          if constexpr (std::tuple_size_v<CandidatesTuple> == 1)
          {
              return action(static_cast<FirstCandidate*>(from));
          }
          else {
              if (fromType == typeOf<FirstCandidate>())
              {
                  return action(static_cast<FirstCandidate*>(from));
              }
              else {
                  return DoComplexDispatchInternal<typename RestTuple<CandidatesTuple>::type, Result>(
                      from, action, fromType
                  );
              }
          }
      }
      
      template<
          typename Calling
          , typename Result
          , typename From
          , typename Action
      >
      inline Result DoComplexDispatch(From* from, Action&& action)
      {
          using ChildsOfCalling = typename ChildClasses<Calling>::type;
          if constexpr (std::tuple_size_v<ChildsOfCalling> == 0)
          {
              return action(static_cast<Calling*>(from));
          }
          else {
              auto fromType = from->GetType();
              using Candidates = decltype(std::tuple_cat(std::declval<std::tuple<Calling>>(), std::declval<ChildsOfCalling>()));
              return DoComplexDispatchInternal<Candidates, Result>(
                  from, std::forward<Action>(action), fromType
              );
          }
      }
      

      我唯一不喜欢的是你必须定义/注册所有子类。

      【讨论】:

        【解决方案14】:

        在虚的情况下如何调用正确的函数?

        Vtable 将包含类的每个虚函数的条目,并且在运行时它将选择特定函数的地址并调用相应的函数。

        在虚函数和函数模板的情况下,如何调用正确的函数?

        如果是函数模板,用户可以用任何类型调用这个函数。这里相同的功能有几个基于类型的版本。现在,在这种情况下,由于版本不同,对于相同的功能,必须维护 vtable 中的许多条目。

        【讨论】:

          【解决方案15】:

          我查看了所有 14 个答案,其中一些有虚拟模板功能无法工作的原因,另一些则显示了解决方法。一个答案甚至表明虚拟类可以具有虚拟功能。这不应该太令人惊讶。

          我的回答将直接说明为什么该标准不允许虚拟模板函数。既然这么多人抱怨。首先,我不敢相信有人评论说可以在编译时推导出虚函数。这是我听过的最愚蠢的话。

          无论如何。我确信标准规定指向对象的 this 指针是其成员函数的第一个参数。

          struct MyClass
          {
           void myFunction();
          }
          
          // translate to
          void myFunction(MyClass*);
          

          现在我们已经很清楚了。然后我们需要知道模板的转换规则。模板化参数极其受限于它可以隐式转换的内容。我不记得全部了,但您可以查看C++ Primer 以获取完整参考。例如 T* 可转换为 const T*。数组可以转换为指针。但是,派生类不能作为模板参数转换为基类。

          struct A {};
          struct B : A {};
          
          template<class T>
          void myFunction(T&);
          
          template<>
          void myFunction<A>(A&) {}
          
          int main()
          {
           A a;
           B b;
          
           myFunction(a); //compiles perfectly
           myFunction((A&)b); // compiles nicely
           myFunction(b); //compiler error, use of undefined template function
          }
          

          所以我希望你明白我的意思。您不能拥有虚拟模板函数,因为就编译器而言,它们是两个完全不同的函数;因为他们隐含的 this 参数是不同的类型。

          虚拟模板不能工作的另一个原因同样有效。由于虚拟表是快速实现虚拟功能的最佳方式。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多