Подтвердить что ты не робот

Защита CRTP-шаблона от в "чистых виртуальных" вызовах

Рассмотрим следующий стандартный пример CRTP:

#include <iostream>

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

struct Foo : public Base<Foo> {
    void f() { std::cout << 42 << std::endl; }
};

int main() {
    Foo foo;
    foo.f(); // just OK
    foo.g(); // this will Qaru and segfault
}

Если это было обычное виртуальное наследование, я мог бы пометить виртуальные методы f и g как чистые, как

struct Base {
    virtual void f() = 0;
    virtual void g() = 0;
};

и получить ошибку времени компиляции Foo как абстрактную. Но CRTP не предлагает такой защиты. Могу ли я каким-то образом реализовать его? Также допустима проверка времени выполнения. Я подумал о сравнении указателя this->f с static_cast<Derived *>(this)->f, но не смог заставить его работать.

4b9b3361

Ответ 1

Вот еще одна возможность:

#include <iostream>

template<class Derived>
struct Base {
    auto f() { return static_cast<Derived *>(this)->f(); }
    auto g() { return static_cast<Derived *>(this)->g(); }
};

struct Foo : public Base<Foo> {
    void f() { std::cout << 42 << std::endl; }
};

int main() {
    Foo foo;
    foo.f(); // just OK
    foo.g(); // this will not compile
}

Для GCC это дает довольно ясное сообщение об ошибке ( "error: использование" auto Base:: g() [с Derived = Foo] "перед выводом" auto "), в то время как для Clang это дает чуть менее читаемый бесконечно рекурсивный экземпляр шаблона Base<Foo>::g, причем g создает экземпляр, но в конечном итоге заканчивается ошибкой.

Ответ 2

Во время компиляции можно утверждать, что два указателя на функции-члены различаются, например:

template<class Derived>
struct Base {
    void g() {
        static_assert(&Derived::g != &Base<Derived>::g,
                      "Derived classes must implement g()."); 
        static_cast<Derived *>(this)->g(); 
    }
};

Ответ 3

Вы можете использовать это решение, у вас может быть чистая "не виртуальная абстрактная" функция, и она максимально отображает CRTP в этой рекомендации . Sutter:

template<class Derived>
struct Base
  {
  void f(){static_cast<Derived*>(this)->do_f();}
  void g(){static_cast<Derived*>(this)->do_g();}

  private:
  //Derived must implement do_f
  void do_f()=delete;
  //do_g as a default implementation
  void do_g(){}
  };

struct derived
  :Base<derived>
  {
  friend struct Base<derived>;

  private:
  void do_f(){}
  };

Ответ 4

Вместо этого вы можете сделать что-то подобное. Вы можете сделать Derived членом и либо предоставить его как параметр шаблона непосредственно каждый раз при создании экземпляра Base, либо использовать псевдоним типа, как я сделал в этом примере:

template<class Derived>
struct Base {
    void f() { d.f(); }
    void g() { d.g(); }
private:
    Derived d;
};

struct FooImpl {
    void f() { std::cout << 42 << std::endl; }
};

using Foo = Base<FooImpl>;

int main() {
    Foo foo;
    foo.f(); // OK
    foo.g(); // compile time error
}

Конечно, Derived больше не выводится, поэтому вы можете выбрать лучшее имя для него.