Защита 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
, но не смог заставить его работать.
Ответы
Ответ 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
больше не выводится, поэтому вы можете выбрать лучшее имя для него.