是否可以使用CRTP在静态多态中模拟纯虚函数?

时间:2015-02-09 05:48:35

标签: c++ static-polymorphism

我正在尝试使用CRTP实现编译时多态,并希望强制派生类实现该函数。

目前的实施是这样的。

template <class Derived>
struct base {
    void f() {
        static_cast<Derived*>(this)->f();
    }
};

struct derived : base<derived>
{
    void f() {
    ...
    }
};

在此实现中,如果派生类未实现f(),则对函数的调用将陷入无限循环。

如何强制派生类实现像纯虚函数一样的函数?我尝试使用像'{1}}这样的'static_assert',但它会生成一条错误消息,指出指向不同类的成员函数的两个成员函数指针不具有可比性。

3 个答案:

答案 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作为返回类型,则此链可以根据需要运行。在某些情况下,它可能既方便又灵活,如以下代码中的BaseLevelTwo所示,当调用同一接口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;
};

在我的情况下,我正在研究的派生类也派生自另一个类,该类提供了确定是在当前类中停止还是派生类所需的额外信息。但是在其他情况下,要么使用实际类型而不是“自动”来打破这种链条,要么使用其他技巧。但是在这种情况下,最好选择虚拟功能