是否可以使用 CRTP 模拟静态多态性中的纯虚函数?
Is emulating pure virtual function in static polymorphism using CRTP possible?
我正在尝试使用 CRTP 实现编译时多态性,并希望强制派生 class 实现该功能。
目前的实现是这样的。
template <class Derived>
struct base {
void f() {
static_cast<Derived*>(this)->f();
}
};
struct derived : base<derived>
{
void f() {
...
}
};
在此实现中,如果派生的 class 未实现 f()
,则对该函数的调用将陷入无限循环。
如何强制派生的 class 像纯虚函数一样实现函数?我尝试像 static_assert(&base::f != &Derived::f, "...")
一样使用 'static_assert' 但它会生成一条错误消息,指出指向不同 classes 的成员函数的两个成员函数指针不可比较。
你可以给你覆盖的东西和钩子不同的名字,像这样:
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
在派生的 class 中被覆盖。
您还可以将中间隐藏层粘贴到您的 CRTP 中,以 "temporarily" 将 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"); }
};
template<typename Derived>
class Base
{
private:
static void verify(void (Derived::*)()) {}
public:
void f()
{
verify(&Derived::f);
static_cast<Derived*>(this)->f();
}
};
如果派生的 class 没有自己实现 f
,&Derived::f
的类型将是 void (Base::*)()
,这会破坏编译。
从 C++11 开始,我们还可以使用可变参数模板使此函数通用。
template<typename Derived>
class Base
{
private:
template<typename T, typename...Args>
static void verify(T (Derived::*)(Args...)) {}
};
尽管这是几年前提出的问题,但我最近遇到了这个问题,所以我就post在这里,希望它能对一些人有所帮助。
使用 auto
作为 return 类型可能是另一种解决方案。考虑以下代码:
template<typename Derived>
class Base
{
public:
auto f()
{
static_cast<Derived*>(this)->f();
}
};
如果派生的 class 没有提供有效的重载,那么这个函数就变成递归的,并且由于 auto
需要最终的 return 类型,所以永远无法推导它,因此将保证抛出编译错误。例如在 MSVC 上它是这样的:
a function that returns 'auto' cannot be used before it is defined
这强制派生 class 提供实现,就像纯虚函数一样。
好处是不需要额外的代码,如果派生的 class 也使用 auto
作为 return 类型,那么这条链可以根据需要走多远。在某些情况下可以方便灵活,如下面代码中的Base
和LevelTwo
,调用同一个接口f
时可以return不同的类型。 但是这个链完全禁止从基础class实现的直接继承,如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;
};
在我的例子中,我工作的派生 class 也派生自另一个 class,它提供了确定是在当前 class 停止还是继续派生 [] 所需的额外信息=37=]。但在其他情况下,要么使用实际类型而不是 'auto' 打破链条,要么使用一些其他技巧。但在这种情况下,也许虚拟函数 是 的最佳选择。
我正在尝试使用 CRTP 实现编译时多态性,并希望强制派生 class 实现该功能。
目前的实现是这样的。
template <class Derived>
struct base {
void f() {
static_cast<Derived*>(this)->f();
}
};
struct derived : base<derived>
{
void f() {
...
}
};
在此实现中,如果派生的 class 未实现 f()
,则对该函数的调用将陷入无限循环。
如何强制派生的 class 像纯虚函数一样实现函数?我尝试像 static_assert(&base::f != &Derived::f, "...")
一样使用 'static_assert' 但它会生成一条错误消息,指出指向不同 classes 的成员函数的两个成员函数指针不可比较。
你可以给你覆盖的东西和钩子不同的名字,像这样:
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
在派生的 class 中被覆盖。
您还可以将中间隐藏层粘贴到您的 CRTP 中,以 "temporarily" 将 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"); }
};
template<typename Derived>
class Base
{
private:
static void verify(void (Derived::*)()) {}
public:
void f()
{
verify(&Derived::f);
static_cast<Derived*>(this)->f();
}
};
如果派生的 class 没有自己实现 f
,&Derived::f
的类型将是 void (Base::*)()
,这会破坏编译。
从 C++11 开始,我们还可以使用可变参数模板使此函数通用。
template<typename Derived>
class Base
{
private:
template<typename T, typename...Args>
static void verify(T (Derived::*)(Args...)) {}
};
尽管这是几年前提出的问题,但我最近遇到了这个问题,所以我就post在这里,希望它能对一些人有所帮助。
使用 auto
作为 return 类型可能是另一种解决方案。考虑以下代码:
template<typename Derived>
class Base
{
public:
auto f()
{
static_cast<Derived*>(this)->f();
}
};
如果派生的 class 没有提供有效的重载,那么这个函数就变成递归的,并且由于 auto
需要最终的 return 类型,所以永远无法推导它,因此将保证抛出编译错误。例如在 MSVC 上它是这样的:
a function that returns 'auto' cannot be used before it is defined
这强制派生 class 提供实现,就像纯虚函数一样。
好处是不需要额外的代码,如果派生的 class 也使用 auto
作为 return 类型,那么这条链可以根据需要走多远。在某些情况下可以方便灵活,如下面代码中的Base
和LevelTwo
,调用同一个接口f
时可以return不同的类型。 但是这个链完全禁止从基础class实现的直接继承,如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;
};
在我的例子中,我工作的派生 class 也派生自另一个 class,它提供了确定是在当前 class 停止还是继续派生 [] 所需的额外信息=37=]。但在其他情况下,要么使用实际类型而不是 'auto' 打破链条,要么使用一些其他技巧。但在这种情况下,也许虚拟函数 是 的最佳选择。