这是多年前提出的一个问题,但我最近遇到了这个问题,所以我将它发布在这里,希望它可以帮助一些人。
使用auto
作为返回类型可能是另一种解决方案。考虑以下代码:
template<typename Derived>
class Base
{
public:
auto f()
{
static_cast<Derived*>(this)->f();
}
};
如果派生类没有提供有效的重载,那么这个函数就变成了递归的,并且由于auto
需要最终的返回类型,它永远不能被推导出来,因此肯定会抛出一个编译错误。例如在 MSVC 上是这样的:
a function that returns 'auto' cannot be used before it is defined
这迫使派生类提供实现,就像纯虚函数一样。
好处是不需要额外的代码,如果派生类也auto
用作返回类型,那么这个链可以根据需要进行。在某些情况下,它可以方便灵活,如Base
在LevelTwo
下面的代码中,在调用相同的接口时可以返回不同的类型f
。然而,这个链完全禁止从基类直接继承实现,如下所示LevelThree
:
template<typename Derived = void>
class Base
{
public:
Base() = default;
~Base() = default;
// interface
auto f()
{
return fImpl();
}
protected:
// implementation chain
auto fImpl()
{
if constexpr (std::is_same_v<Derived, void>)
{
return int(1);
}
else
{
static_cast<Derived*>(this)->fImpl();
}
}
};
template<typename Derived = void>
class LevelTwo : public Base<LevelTwo>
{
public:
LevelTwo() = default;
~LevelTwo() = default;
// inherit interface
using Base<LevelTwo>::f;
protected:
// provide overload
auto fImpl()
{
if constexpr (std::is_same_v<Derived, void>)
{
return float(2);
}
else
{
static_cast<Derived*>(this)->fImpl();
}
}
friend Base;
};
template<typename Derived = void>
class LevelThree : public LevelTwo<LevelThree>
{
public:
LevelThree() = default;
~LevelThree() = default;
using LevelTwo<LevelThree>::f;
protected:
// doesn't provide new implementation, compilation error here
using LevelTwo<LevelThree>::fImpl;
friend LevelTwo;
};
在我的例子中,我处理的派生类也派生自另一个类,该类提供了确定是停止在当前类还是转到派生类所需的额外信息。但在其他情况下,要么使用实际类型而不是“自动”来打破链条,要么使用其他一些技巧。但在这种情况下,也许虚函数是最好的选择。