10

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

当前的实现是这样的。

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

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

在这个实现中,如果派生类没有实现,对函数的调用就会陷入死循环f()

如何强制派生类实现纯虚函数之类的功能?我尝试使用'static_assert',static_assert(&base::f != &Derived::f, "...")但它会生成一条错误消息,指出指向不同类的成员函数的两个成员函数指针不具有可比性。

4

3 回答 3

9

你可以给你覆盖的东西和钩子不同的名字,像这样:

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 中以“临时”标记fdelete

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"); }
};
于 2015-02-09T06:00:26.543 回答
3
template<typename Derived>
class Base
{
  private:
    static void verify(void (Derived::*)()) {}

  public:
    void f()
    {
        verify(&Derived::f);
        static_cast<Derived*>(this)->f();
    }
};

如果派生类不自行实现f,则类型为,这&Derived::fvoid (Base::*)()破坏编译。

从 C++11 开始,我们也可以使用可变参数模板使这个函数成为泛型。

template<typename Derived>
class Base
{
  private:
    template<typename T, typename...Args>
    static void verify(T (Derived::*)(Args...)) {}
};
于 2016-09-30T09:24:35.437 回答
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;
};

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

于 2019-11-29T06:17:39.373 回答