c++ - 尝试根据模板类型在模板类中重载虚函数
问题描述
我今天有额外的有趣的恶作剧。
我对 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”是否存在而改变,而且我不喜欢它是如何完成的。我觉得我必须在这里遗漏一些简单的东西。
解决方案
Your idea to do type checking is valid, but your approaches need a little more work to help the compiler.
If you are using C++17 or later, use if constexpr
with std::is_same_v
, eg:
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;
};
The compiler will evaluate the if constexpr
completely at compile-time and eliminate the unused branch in the final runtime code, thus producing different code for each instantiation of myClass<T>
, eg:
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;
};
If using C++17 or later is not an option for you, then you can use reinterpret_cast
instead, eg:
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;
};
Without the cast, the compiler would produce code like the following for each instantiation of myClass<T>
:
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;
};
The same issue happens when passing position
to non-template member methods instead, eg:
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;
};
Without the casts, the compiler would produce code like the following for each instantiation of myClass<T>
:
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;
};
That being said, another option would be to use template specialization, then no funky casts are needed at all, eg:
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;
};
The compiler would produce code like the following for each instantiation of myClass<T>
:
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;
};
Which, after the compiler inlines the specialized functions at the call sites, would look very familiar 1
1: <cough> the C++17 if constexpr
output !
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 - c中的字数统计程序(它应该排除注释)
- javascript - 如何在我的反应应用程序中修复 eslint 错误“未定义 pendo”
- javascript - 我如何向另一个客户显示联合国图像
- php - 调用字符串错误上的成员函数 format()
- dart - DropDownButton 所选项目在 UI 中没有改变
- amazon-web-services - 使用 Amazon Sagemaker 基本事实自动标记文本数据
- python - 高效的字节查找表
- windows - 尝试使用 powershell 自动填充网页 a 的输入字段。但卡住了错误
- sql - 如果状态不时发生变化,我如何才能看到特定状态的正确数量(历史数据)
- reactjs - 除非刷新浏览器,否则当新版本移至生产时,我无法看到我的最新更改