我正在尝试使用CRTP实现编译时多态,并希望强制派生类实现该函数。
目前的实施是这样的。
template <class Derived>
struct base {
void f() {
static_cast<Derived*>(this)->f();
}
};
struct derived : base<derived>
{
void f() {
...
}
};
在此实现中,如果派生类未实现f()
,则对函数的调用将陷入无限循环。
如何强制派生类实现像纯虚函数一样的函数?我尝试使用像'{1}}这样的'static_assert',但它会生成一条错误消息,指出指向不同类的成员函数的两个成员函数指针不具有可比性。
答案 0 :(得分:8)
你可以给你覆盖的东西和钩子不同的名字,如下所示:
template <class Derived>
struct base {
void f() {
static_cast<Derived*>(this)->fimpl();
}
void fimpl() = delete;
};
struct derived : base<derived> {
void fimpl() { printf("hello world\n"); }
};
此处,fimpl = delete
在基础中,因此除非在派生类中重写fimpl
,否则无法意外调用它。
你也可以将一个中间隐藏层粘贴到你的CRTP中,暂时&#34;将f
标记为delete
:
template <class Derived>
struct base {
void f() {
static_cast<Derived*>(this)->f();
}
};
template <class Derived>
struct intermediate : base<Derived> {
void f() = delete;
};
struct derived : intermediate<derived> {
void f() { printf("hello world\n"); }
};
答案 1 :(得分:2)
template<typename Derived>
class Base
{
private:
static void verify(void (Derived::*)()) {}
public:
void f()
{
verify(&Derived::f);
static_cast<Derived*>(this)->f();
}
};
如果派生类本身没有实现f
,则&Derived::f
的类型将为void (Base::*)()
,这会破坏编译。
从C ++ 11开始,我们也可以使用variadic模板使这个函数通用。
template<typename Derived>
class Base
{
private:
template<typename T, typename...Args>
static void verify(T (Derived::*)(Args...)) {}
};
答案 2 :(得分:0)
这是几年前提出的问题,但是最近我遇到了,所以我将其张贴在这里,希望对某些人有所帮助。
使用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;
};
在我的情况下,我正在研究的派生类也派生自另一个类,该类提供了确定是在当前类中停止还是派生类所需的额外信息。但是在其他情况下,要么使用实际类型而不是“自动”来打破这种链条,要么使用其他技巧。但是在这种情况下,最好选择虚拟功能 。