【问题标题】:Trying to overload a virtual function in a template class based on template type [duplicate]尝试根据模板类型在模板类中重载虚函数[重复]
【发布时间】:2020-10-15 23:57:59
【问题描述】:

我今天有额外的有趣的恶作剧。

我对 C++ 中的模板有些陌生。以下是我的代码中目前存在的一些类:

class location2d
{
    int x, y;
}

class location3d
{
    int x, y, z;
}

template <typename T>
class myClass : public parentClass<float, T>
{
    private:

    virtual void myFunction (T position) const override final
    {
        // some math stuff (this part doesn't matter)
        something = position.x + position.y;
    }

    int something;
};

现在这是硬编码到 location2d。如果传入 location3d,我需要 myFunction() 具有不同的行为。例如:

virtual void myFunction (T position) const override final
{
    something = position.x + position.y + position.z;
}

我已经阅读了模板专业化,但这变得很棘手,因为 myFunction() 覆盖了基类中的虚函数。我的理解是我们不能专门化一个虚函数。反正我试过了。它讨厌它。

我的第二个想法是键入检查模板并调用单独的帮助器:

virtual void myFunction (T position) const override final
{
    if (std::is_same<T, location3d) {myFunction3(position);}
    else {myFunction2(position);}
}

void myFunction2 (T position) const
{
    something = position.x + position.y;
}

void myFunction3 (T position) const
{
    something = position.x + position.y + position.z;
}

这里的问题是编译器抛出“location2d 不包含成员'z'”,这是绝对正确的。但是,除非 z 存在,否则不会调用 myFunction3()。

接下来我尝试专门进行强制转换,以便 T 不再模棱两可:

virtual void myFunction (T position) const override final
{
    if (std::is_same<T, location3d>::value) {myFunction3((location3d)position);}
    else {myFunction2((location2d)position);}
}

"'type cast': 无法从 'T' 转换为 'location3'"。

最后的想法: 由于辅助函数不是虚拟的,也许我可以专门化这两个函数。

virtual void myFunction (T position) const override final
{
    if (std::is_same<T, location3d>::value) {mySecondFunction<location3d>(position);}
    else {mySecondFunction<location2d>(position);}
}

template<>
void mySecondFunction<location2d> (location2d position) const {}

template<>
void mySecondFunction<location3d> (location3d position) const {}

我不确定我是否做错了,但它引发了大量我不知道如何修复的语法错误。

归根结底,我只想让 myFunction() 的行为根据 'z' 是否存在而改变,而且我不喜欢它是如何完成的。我觉得我必须在这里遗漏一些简单的东西。

【问题讨论】:

  • 可以用c++17吗?另外,请发minimal reproducible exampleparentClass是什么?
  • 您需要constexpr if 或模板专业化。其中一个分支仍在尝试编译,而另一个分支将满足您的需求。 std::enable_if 是另一种选择。
  • 为什么不能“专门化一个虚函数”?首先:在这种情况下,您专门化了类而不是函数本身,这在这里很重要。你可以在你的专业化中做任何你想做的事情,其中​​包括来自父类的重写函数。专业化与“基本模板”没有任何关系;它就像一个完全不同的类,如果您的模板参数与您专门用于的参数匹配,则会使用它。
  • 如果您不方便替换整个类并且您只想更改单个函数,您可以将通用代码放在另一个(中间)基类中,或者您可以使用 C 中的 constexpr if正如@πάνταῥεῖ 已经指出的那样,++17 开始。旧方法是手动使用std::enable_if 或 SFINAE,但constexpr if 更方便。
  • 另外,您似乎在这里有一个重大的误解:TmyClass 的模板参数,而myClass&lt;T&gt;::myFunction 不是函数模板。它就像myClass 的任何其他方法一样。因此也不可能专门化myClass&lt;T&gt;::myFunction

标签: c++ templates template-specialization virtual-functions


【解决方案1】:

您进行类型检查的想法是有效的,但您的方法需要更多的工作来帮助编译器。

如果您使用的是 C++17 或更高版本,请使用 if constexprstd::is_same_v,例如:

template <typename T>
class myClass : public parentClass<float, T>
{
private:
    virtual void myFunction (T position) const override final
    {
        if constexpr (std::is_same_v<T, location3d>) {
            something = position.x + position.y + position.z;
        }
        else {
            something = position.x + position.y;
        }
    }

    int something;
};

编译器将在编译时完全评估if constexpr,并消除最终运行时代码中未使用的分支,从而为myClass&lt;T&gt; 的每个实例生成不同的代码,例如:

class myClass<location2d> : public parentClass<float, location2d>
{
private:
    virtual void myFunction (location2d position) const override final
    {
        something = position.x + position.y;
    }

    int something;
};

class myClass<location3d> : public parentClass<float, location3d>
{
private:
    virtual void myFunction (location3d position) const override final
    {
        something = position.x + position.y + position.z;
    }

    int something;
};

如果您不适合使用 C++17 或更高版本,则可以改用 reinterpret_cast,例如:

template <typename T>
class myClass : public parentClass<float, T>
{
private:
    virtual void myFunction (T position) const override final
    {
        if (std::is_same<T, location3d>::value) {
            // if T is NOT location3d then accessing position.z as-is
            // will fail to compile if T::z is missing, hence the cast.
            // Since this branch is executed only when T is location3d,
            // the cast in this branch is redundant but harmless. But
            // this branch is still compiled even when T is NOT loction3d...
            something = position.x + position.y + reinterpret_cast<location3d&>(position).z;
        }
        else {
            // no cast is needed here since location2d and location3d
            // both have x and y fields...
            something = position.x + position.y;
        }
    }

    int something;
};

如果没有强制转换,编译器将为myClass&lt;T&gt; 的每个实例生成如下代码:

class myClass<location2d> : public parentClass<float, location2d>
{
private:
    virtual void myFunction (location2d position) const override final
    {
        if (false) {
            something = position.x + position.y + position.z; // ERROR! location2d::z does not exist...
        }
        else {
            something = position.x + position.y; // OK
        }
    }

    int something;
};

class myClass<location3d> : public parentClass<float, location3d>
{
private:
    virtual void myFunction (location3d position) const override final
    {
        if (true) {
            something = position.x + position.y + position.z; // OK
        }
        else {
            something = position.x + position.y; // OK
        }
    }

    int something;
};

position 传递给非模板成员方法时会发生同样的问题,例如:

template <typename T>
class myClass : public parentClass<float, T>
{
private:
    virtual void myFunction (T position) const override final
    {
        if (std::is_same<T, location3d) {
            // if T is NOT location3d, passing position as-is to myFunction3()
            // would fail to compile, hence the cast. Since this branch is
            // executed only when T is location3d, the cast in this branch
            // is redundant but harmless. But this branch is still compiled
            // even when T is NOT loction3d...
            myFunction3(reinterpret_cast<location3d&>(position));
        }
        else {
            // if T is NOT location2d, passing position as-is to myFunction2()
            // would fail to compile, hence the cast. Since this branch is
            // executed only when T is location2d, the cast in this branch
            // is redundant but harmless. But this branch is still compiled
            // even when T is NOT location2d...
            myFunction2(reinterpret_cast<location2d>(position));
        }
    }

    void myFunction2 (location2d position)
    {
        something = position.x + position.y;
    }

    void myFunction3 (location3d position)
    {
        something = position.x + position.y + position.z;
    }

    int something;
};

如果没有强制转换,编译器将为myClass&lt;T&gt; 的每个实例生成如下代码:

class myClass<location2d> : public parentClass<float, location2d>
{
private:
    virtual void myFunction (location2d position) const override final
    {
        if (false) {
            myFunction3(position); // ERROR! can't convert from location2d to location3d
        }
        else {
            myFunction2(position); // OK
        }
    }

    void myFunction2 (location2d position)
    {
        something = position.x + position.y;
    }

    void myFunction3 (location3d position)
    {
        something = position.x + position.y + position.z;
    }

    int something;
};

class myClass<location3d> : public parentClass<float, location3d>
{
private:
    virtual void myFunction (location3d position) const override final
    {
        if (true) {
            myFunction3(position); // OK
        }
        else {
            myFunction2(position); // ERROR! can't convert from location3d to location2d
        }
    }

    void myFunction2 (location2d position)
    {
        something = position.x + position.y;
    }

    void myFunction3 (location3d position)
    {
        something = position.x + position.y + position.z;
    }

    int something;
};

话虽如此,另一种选择是使用模板专业化,然后根本不需要时髦的演员表,例如:

template<typename T>
int add_them_up(T) { return 0; }

template<>
int add_them_up<location2d>(location2d position)
{
    return position.x + position.y;
}

template<>
int add_them_up<location3d>(location3d position)
{
    return position.x + position.y + position.z;
}

template <typename T>
class myClass : public parentClass<float, T>
{
private:
    virtual void myFunction (T position) const override final
    {
        something = add_them_up<T>(position);
    }

    int something;
};

编译器将为myClass&lt;T&gt; 的每个实例生成如下代码:

int add_them_up<location2d>(location2d position)
{
    return position.x + position.y;
}

int add_them_up<location3d>(location3d position)
{
    return position.x + position.y + position.z;
}

class myClass<location2d> : public parentClass<float, location2d>
{
private:
    virtual void myFunction (location2d position) const override final
    {
        something = add_them_up<location2d>(position);
    }

    int something;
};

class myClass<location3d> : public parentClass<float, location3d>
{
private:
    virtual void myFunction (location3d position) const override final
    {
        something = add_them_up<location3d>(position);
    }

    int something;
};

编译器在调用站点内联专用函数后,看起来非常熟悉 1

1: C++17 if constexpr 输出!

class myClass<location2d> : public parentClass<float, location2d>
{
private:
    virtual void myFunction (location2d position) const override final
    {
        something = position.x + position.y;
    }

    int something;
};

class myClass<location3d> : public parentClass<float, location3d>
{
private:
    virtual void myFunction (location3d position) const override final
    {
        something = position.x + position.y + position.z;
    }

    int something;
};

【讨论】:

  • 一个奇妙而详细的答案!我不知道 constexpr,所以这是成功的一半。另一半是激光。我会记住这些其他建议以备将来使用。谢谢!
猜你喜欢
  • 2013-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-26
  • 2021-03-19
  • 1970-01-01
相关资源
最近更新 更多