我的一个朋友问我“如何使用CRTP替换多级继承中的多态”。更确切地说,在这种情况下:
struct A {
void bar() {
// do something and then call foo (possibly) in the derived class:
foo();
}
// possibly non pure virtual
virtual void foo() const = 0;
}
struct B : A {
void foo() const override { /* do something */ }
}
struct C : B {
// possibly absent to not override B::foo().
void foo() const final { /* do something else */ }
}
我的朋友和我都知道CRTP不是多态性的替代品,但我们对可以使用这两种模式的情况感兴趣。 (为了这个问题,我们对每种模式的利弊都不感兴趣。)
答案 0 :(得分:12)
(1)层次结构中最顶层的类看起来像:
template <typename T>
class A {
public:
void bar() const {
// do something and then call foo (possibly) in the derived class:
foo();
}
void foo() const {
static_cast<const T*>(this)->foo();
}
protected:
~A() = default;
// Constructors should be protected as well.
};
A<T>::foo()
的行为类似于纯虚方法,因为它没有“默认实现”,并且调用被定向到派生类。但是,这不会阻止A<T>
实例化为非基类。要获得此行为A<T>::~A()
,请protected
。
备注:不幸的是,GCC bug在使用= default;
时会将特殊成员函数公开。在这种情况下,应该使用
protected:
~A() {}
尽管如此,保护析构函数还不足以满足对构造函数的调用与析构函数调用不匹配的情况(这可能通过operator new
发生)。因此,建议保护所有构造函数(包括复制和移动构造函数)。
当应允许A<T>
的实例化并且A<T>::foo()
应该像非纯虚方法一样时,A
应该类似于下面的模板类B
。
(2)层次结构中间的类(或最上面的类,如上段所述)如下所示:
template <typename T = void>
class B : public A<B<T>> { // no inherinace if this is the topmost class
public:
// Constructors and destructor
// boilerplate code :-(
void foo() const {
foo_impl(std::is_same<T, void>{});
}
private:
void foo_impl(std::true_type) const {
std::cout << "B::foo()\n";
}
// boilerplate code :-(
void foo_impl(std::false_type) const {
if (&B::foo == &T::foo)
foo_impl(std::true_type{});
else
static_cast<const T*>(this)->foo();
}
};
构造函数和析构函数是公开的,T
默认为void
。这允许类型为B<>
的对象在层次结构中派生得最多,并使其合法化:
B<> b;
b.foo();
另请注意,B<T>::foo()
表现为非纯虚拟方法,如果B<T>
是派生程度最高的类(或者更准确地说,如果T
是{{1} }}),然后void
调用 b.foo();
的默认实现“(输出foo()
)。如果B::foo()
不是T
,则调用将定向到派生类。这是通过标签调度来完成的。
需要测试void
以避免无限递归调用。实际上,如果派生类&B::foo == &T::foo
未重新实现T
,则调用foo()
将解析为再次调用static_cast<const T*>(this)->foo();
的{{1}}。此外,此测试可以在编译时解决,代码可以是B::foo()
或B::foo_impl(std::false_type)
,优化程序可以完全删除测试(例如GCC与-O3)。
(3)最后,层次结构的底部如下所示:
if (true)
或者,如果继承的实现(if (false)
)足够,可以完全删除class C : public B<C> {
public:
void foo() const {
std::cout << "C::foo()\n";
}
};
。
请注意,C::foo()
类似于最终方法,因为调用它并不会将调用重定向到派生类(如果有的话)。 (为了使其不是最终的,应该使用像B<C>::foo()
这样的模板类。)
(4)另见:
答案 1 :(得分:9)
注意:这并不是&#34;最终覆盖&#34;的解决方案。问题,但一般来说CRTP多级继承问题(因为我没有在任何地方找到答案如何做,我认为我的发现会很有用)。
编辑:我已发布最终覆盖问题的解决方案here
我最近了解到CRTP及其作为运行时多态性的静态替代品的潜力。在搜索了一段时间之后,看看CRTP是否可以用作类似的&#34; drop-in&#34;更换多态,这样你可以使用多级继承等,我不得不说,我很惊讶我无法在没有样板的情况下找到一个合适的通用解决方案,这可能无限扩展。毕竟,为什么不尝试使CRTP成为多态性的替代品,因为它具有所有性能优势?随后进行了一些调查,这就是我想出的:
问题:
经典的CRTP模式创建了一个&#34;循环&#34; CRTP接口和实现类之间的可访问性。 (CRTP接口类可以通过静态强制转换为模板参数类型访问&#34; base&#34;实现类,实现类从CRTP接口类继承公共接口。)创建时具体的实现,你正在关闭循环,使得从具体的实现类继承非常困难,因此从它派生的任何东西也都以多态的方式运行。
Classic CRTP single-level inheritance
解决方案:
将模式分为三个概念:
这是一个有助于说明的图表:
Multiple-level inheritance with CRTP
诀窍是将具体实现类作为模板参数传递到所有可继承的实现类,直到抽象接口类。
通过这种方法,您可以:
完美地反映了虚拟/运行时多态性。
示例代码:
#include <iostream>
template <class Top>
struct CrtpInterface
{
void foo()
{
std::cout << "Calling CrtpInterface::foo()\n";
fooImpl();
}
void foo2()
{
std::cout << "Calling CrtpInterface::foo2()\n";
fooImpl2();
}
void foo3()
{
std::cout << "Calling CrtpInterface::foo3()\n";
fooImpl3();
}
void foo4()
{
std::cout << "Calling CrtpInterface::foo4()\n";
fooImpl4();
}
// The "pure virtual functions"
protected:
inline void fooImpl()
{
top().fooImpl();
}
inline void fooImpl2()
{
top().fooImpl2();
}
inline void fooImpl3()
{
top().fooImpl3();
}
inline void fooImpl4()
{
top().fooImpl4();
}
inline Top& top()
{
return static_cast<Top&>(*this);
}
};
template<class Top>
class DefaultImpl : public CrtpInterface<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
void fooImpl()
{
std::cout << "Default::fooImpl()\n";
}
void fooImpl2()
{
std::cout << "Default::fooImpl2()\n";
std::cout << "Calling foo() from interface\n";
impl::foo();
}
void fooImpl3()
{
std::cout << "Default::fooImpl3()\n";
std::cout << "Calling highest level fooImpl2() from interface\n";
impl::fooImpl2();
}
void fooImpl4()
{
std::cout << "Default::fooImpl4()\n";
std::cout << "Calling highest level fooImpl3() from interface\n";
impl::fooImpl3();
}
};
template<class Top>
class AImpl : public DefaultImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
void fooImpl()
{
std::cout << "A::fooImpl()\n";
}
};
struct A : AImpl<A>
{
};
template<class Top>
class BImpl : public AImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
protected:
BImpl()
: i{1}
{
}
private:
int i;
void fooImpl2()
{
std::cout << "B::fooImpl2(): " << i << "\n";
}
};
struct B : BImpl<B>
{
};
template<class Top>
class CImpl : public BImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
protected:
CImpl(int x = 2)
: i{x}
{
}
private:
int i;
void fooImpl3()
{
std::cout << "C::fooImpl3(): " << i << "\n";
}
};
struct C : CImpl<C>
{
C(int i = 9)
: CImpl(i)
{
}
};
template<class Top>
class DImpl : public CImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
void fooImpl4()
{
std::cout << "D::fooImpl4()\n";
}
};
struct D : DImpl<D>
{
};
int main()
{
std::cout << "### A ###\n";
A a;
a.foo();
a.foo2();
a.foo3();
a.foo4();
std::cout << "### B ###\n";
B b;
b.foo();
b.foo2();
b.foo3();
b.foo4();
std::cout << "### C ###\n";
C c;
c.foo();
c.foo2();
c.foo3();
c.foo4();
std::cout << "### D ###\n";
D d;
d.foo();
d.foo2();
d.foo3();
d.foo4();
}
打印哪些:
### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
D::fooImpl4()
使用这种方法,以及&#34;变体式&#34;包装器(使用一些sechsy可变参数模板和宏构建,也许我稍后会发布),其作用类似于指向虚拟抽象基类的指针,我能够有效地创建从同一接口继承的CRTP类的向量
我测量了与类似的虚拟类的向量相比较的性能,所有这些都基于等效的虚拟接口,我发现使用这种方法,根据场景,我可以实现性能提升到8倍!考虑到生成功能多态CRTP类层次结构所需的开销相对较少,这非常令人鼓舞!
答案 2 :(得分:3)
在意识到我的original answer没有真正处理手头的最终覆盖问题之后,我想我会添加它。我想提出一个&#34;最终覆盖&#34;解决方案与我之前的回答类似。
问题:
CRTP接口类始终通过静态强制转换重定向到最高派生类。这与&#34; final&#34;的概念不一致。功能:如果想要&#34;最终&#34;函数未在最高派生类上实现,并且被覆盖&#34;由于更高级别(因为你不能给函数一个&#34; final&#34;属性,除非它是虚拟的,我们在CRTP中试图避免),CRTP接口将不会重定向到所需的&#34; final& #34;功能,但&#34;覆盖&#34;。
解决方案:
将界面分为三个概念:
实例化具体实现类时,不是通过所有可继承的实现类将#具体实现类作为模板参数传递给#34;在接口中,我们传递接口将继承的重定向类作为模板参数。
当我们想要创建一个函数&#34; final&#34;时,我们只需创建一个&#34;重定向覆盖类&#34;它继承自抽象重定向类,并覆盖我们想要最终的重定向函数。然后我们传递这个新的&#34;重定向覆盖类&#34;作为所有可继承实现类的参数。
采用这种方法:
这一切听起来都很复杂,所以这是我为了让事情更容易理解而制作的流程图:
DImpl和EImpl具有最终功能,当DImpl或EImpl继承自:时无法覆盖
示例代码:
#include <iostream>
#include <type_traits>
template <class Top>
struct Redirect
{
protected:
// The "pure virtual functions"
inline void fooImpl()
{
top().fooImpl();
}
inline void fooImpl2()
{
top().fooImpl2();
}
inline void fooImpl3()
{
top().fooImpl3();
}
inline void fooImpl4()
{
top().fooImpl4();
}
inline Top& top()
{
// GCC doesn't allow static_cast<Top&>(*this)
// since Interface uses private inheritance
static_assert(std::is_base_of<Redirect, Top>::value, "Invalid Top class specified.");
return (Top&)(*this);
}
};
// Wraps R around the inner level of a template T, e.g:
// R := Redirect, T := X, then inject_type::type := Redirect<X>
// R := Redirect, T := A<B<C<X>>>, then inject_type::type := A<B<C<Redirect<X>>>>
template<template<class> class R, class T>
struct inject_type
{
using type = R<T>;
};
template<template<class> class R, class InnerFirst, class... InnerRest, template<class...> class Outer>
struct inject_type<R, Outer<InnerFirst, InnerRest...>>
{
using type = Outer<typename inject_type<R, InnerFirst>::type, InnerRest...>;
};
// We will be inheriting either Redirect<...> or something
// which derives from it (and overrides the functions).
// Use private inheritance, so that all polymorphic calls can
// only go through this class (which makes it impossible to
// subvert redirect overrides using future user code).
template <class V>
struct Interface : private inject_type<Redirect, V>::type
{
using impl = Interface;
void foo()
{
std::cout << "Calling Interface::foo()\n";
fooImpl();
}
void foo2()
{
std::cout << "Calling Interface::foo2()\n";
fooImpl2();
}
void foo3()
{
std::cout << "Calling Interface::foo3()\n";
fooImpl3();
}
void foo4()
{
std::cout << "Calling Interface::foo4()\n";
fooImpl4();
}
private:
using R = typename inject_type<::Redirect, V>::type;
protected:
using R::fooImpl;
using R::fooImpl2;
using R::fooImpl3;
using R::fooImpl4;
};
template<class V>
struct DefaultImpl : Interface<V>
{
template<class>
friend struct Redirect;
protected:
// Picking up typename impl from Interface, where all polymorphic calls must pass through
using impl = typename DefaultImpl::impl;
void fooImpl()
{
std::cout << "Default::fooImpl()\n";
}
void fooImpl2()
{
std::cout << "Default::fooImpl2()\n";
std::cout << "Calling foo() from interface\n";
impl::foo();
}
void fooImpl3()
{
std::cout << "Default::fooImpl3()\n";
std::cout << "Calling highest level fooImpl2() from interface\n";
impl::fooImpl2();
}
void fooImpl4()
{
std::cout << "Default::fooImpl4()\n";
std::cout << "Calling highest level fooImpl3() from interface\n";
impl::fooImpl3();
}
};
template<class V>
struct AImpl : public DefaultImpl<V>
{
template<class>
friend struct Redirect;
protected:
void fooImpl()
{
std::cout << "A::fooImpl()\n";
}
};
struct A : AImpl<A>
{
};
template<class V>
struct BImpl : public AImpl<V>
{
template<class>
friend struct Redirect;
protected:
BImpl()
: i{1}
{
}
private:
int i;
void fooImpl2()
{
std::cout << "B::fooImpl2(): " << i << "\n";
}
};
struct B : BImpl<B>
{
};
template<class V>
struct CImpl : public BImpl<V>
{
template<class>
friend struct Redirect;
protected:
CImpl(int x = 2)
: i{x}
{
}
private:
int i;
void fooImpl3()
{
std::cout << "C::fooImpl3(): " << i << "\n";
}
};
struct C : CImpl<C>
{
C(int i = 9)
: CImpl(i)
{
}
};
// Make D::fooImpl4 final
template<class V>
struct DImplFinal : public V
{
protected:
void fooImpl4()
{
std::cout << "DImplFinal::fooImpl4()\n";
}
};
// Wrapping V with DImplFinal overrides the redirecting functions
template<class V>
struct DImpl : CImpl<DImplFinal<V>>
{
};
struct D : DImpl<D>
{
};
template<class V>
struct EImpl : DImpl<V>
{
template<class>
friend struct Redirect;
protected:
void fooImpl()
{
std::cout << "E::fooImpl()\n";
}
void fooImpl3()
{
std::cout << "E::fooImpl3()\n";
}
// This will never be called, because fooImpl4 is final in DImpl
void fooImpl4()
{
std::cout << "E::fooImpl4(): this should never be printed\n";
}
};
struct E : EImpl<E>
{
};
// Make F::fooImpl3 final
template<class V, class Top>
struct FImplFinal : public V
{
protected:
// This is implemented in FImpl, so redirect
void fooImpl3()
{
top().fooImpl3();
}
// This will never be called, because fooImpl4 is final in DImpl
void fooImpl4()
{
std::cout << "FImplFinal::fooImpl4() this should never be printed\n";
}
inline Top& top()
{
// GCC won't do a static_cast directly :(
static_assert(std::is_base_of<FImplFinal, Top>::value, "Invalid Top class specified");
return (Top&)(*this);
}
};
// Wrapping V with FImplFinal overrides the redirecting functions, but only if they haven't been overridden already
template<class V>
struct FImpl : EImpl<FImplFinal<V, FImpl<V>>>
{
template<class>
friend struct Redirect;
template<class, class>
friend struct FImplFinal;
protected:
FImpl()
: i{99}
{
}
// Picking up typename impl from DefaultImpl
using impl = typename FImpl::impl;
private:
int i;
void fooImpl2()
{
std::cout << "F::fooImpl2()\n";
// This will only call DFinal::fooImpl4();
std::cout << "Calling fooImpl4() polymorphically. (Should not print FImplFinal::fooImpl4() or EImpl::fooImpl4())\n";
impl::fooImpl4();
}
void fooImpl3()
{
std::cout << "FImpl::fooImpl3(), i = " << i << '\n';
}
};
struct F : FImpl<F>
{
};
int main()
{
std::cout << "### A ###\n";
A a;
a.foo();
a.foo2();
a.foo3();
a.foo4();
std::cout << "### B ###\n";
B b;
b.foo();
b.foo2();
b.foo3();
b.foo4();
std::cout << "### C ###\n";
C c;
c.foo();
c.foo2();
c.foo3();
c.foo4();
std::cout << "### D ###\n";
D d;
d.foo();
d.foo2();
d.foo3();
d.foo4();
std::cout << "### E ###\n";
E e;
e.foo();
e.foo2();
e.foo3();
e.foo4();
std::cout << "### F ###\n";
F f;
f.foo();
f.foo2();
f.foo3();
f.foo4();
}
代码打印:
### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### E ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
E::fooImpl3()
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### F ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
F::fooImpl2()
Attempting to call FFinal::fooImpl4() or E::fooImpl4()
DImplFinal::fooImpl4()
Calling CrtpInterface::foo3()
FImpl::fooImpl3(), i = 99
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
答案 3 :(得分:3)
template<class Derived>
struct A {
void foo() {
static_cast<Derived*>(this)->foo();
}
};
template<class Derived>
struct B: A <Derived> {
void foo() {
// do something
}
};
struct C: B <C> {
void foo(); // can be either present or absent
};
如果C中的foo()不存在,则将调用B中的一个。否则B中的那个将被覆盖。
答案 4 :(得分:1)
多级继承不是问题,但CRTP如何创建多态是。
template<typename Derived>
struct Base
{
void f() { /* Basic case */ }
// "Pure virtual" method
void pure() { static_cast<Derived*>(this)->pure(); }
};
struct Overriding: Base<Overriding>
{
void f() { /* Special case */ }
// This method must exists to prevent endless recursion in Base::f
void pure() { /* ... */ }
};
struct NonOverriding: Base<NonOverriding>
{
void pure() { /* ... */ }
};
template<typename Derived>
void f(const Base<Derived>& base)
{
base.f(); // Base::f
base.pure(); // Base::pure, which eventually calls Derived::pure
// Derived::f if an overriding method exists.
// Base::f otherwise.
static_cast<const Derived&>(base).f();
}
还可以引入derived
方法,以避免在每次调用时出现冗长的类型。
template<typename Derived>
struct Base
{
Derived& derived() { return *static_cast<Derived*>(this); }
const Derived& derived() const { return *static_cast<const Derived*>(this); }
};
答案 5 :(得分:0)
这是一个可能的实现,可以减少类中的样板代码(但不是辅助代码的总量)。
此解决方案的想法是使用SFINAE和重载来选择impl函数。
(i)A类
template <typename T> class A {
void foo() const {
static_cast<const T*>(this)->foo( Type2Type<T> );
}
}
其中TypetoType是模板struct
template< typename T > struct Type2Type {
typedef T OriginalType
}
这对于帮助编译器选择foo()impl非常有用。通过重载。
(i)B类
template <typename T = void>
class B : public A<B<T>> {
void foo(...) {
std::cout << "B::foo()\n";
}
void foo( Type2Type< std::enable_if< is_base_of<T,B>::value == true, B>::type> ) {
static_cast<const T*>(this)->foo( Type2Type<T> );
}
}
此处,如果层次结构的底部由C给出,则不需要TypetoType参数。
(ii)C类
class C : public B<C> {
void foo(...) {
std::cout << "C::foo()\n";
}
}
我知道如果T == B,std :: is_base_of会返回true。在这里,我们使用我们自己的is_base_of版本,当两个模板参数相同时返回false_type。像
这样的东西template<class B, class D>
struct is_base_of : public is_convertible<D *, B *> {};
template<class B, class B>
struct is_base_of<B, B> : public false_type {};
template<class D>
struct is_base_of<void, D> : public false_type {};
结论:如果std :: enable_if失败,那么foo()的可变版本将是唯一可用的版本(因为SFINAE),编译器将实现B版本的foo。但是,如果enable_if没有失败,编译器将选择第二个版本(因为当编译器试图找出重载函数之间的最佳匹配时,variadic是最后一个选项)。
答案 6 :(得分:0)
此线程中发生了很多事情,我认为这些事情没有用,所以我在这里分享我对这个问题的解决方案。
CRTP主要是减少代码的一种模式。为了正常工作,有必要在继承层次结构的每个级别上都可以从下面的级别调用所有函数-就像通常的动态继承一样。
但是,在CRTP中,每个阶段都必须进一步了解当前从其派生的最终类型,因为最后这就是CRTP的重点-调用始终应用于当前(静态)的函数最终类型。
可以通过在静态继承层次结构的每个级别上添加一个间接层来实现此目的,如以下示例所示:
template<typename derived_t>
struct level0_impl
{
auto const& derived() const
{
return static_cast<derived_t const&>(*this);
}
};
struct level0 : public level0_impl<level0>
{
using level0_impl<level0>::level0_impl;
};
template<typename derived_t>
struct level1_impl : level0_impl<derived_t>
{
auto only_for_level1_and_derived() const
{
return derived().foo;
};
auto do_something() const { std::cout<<"hi"<<std::endl; }
};
struct level1 : public level1_impl<level1>
{
using level1_impl<level1>::level1_impl;
};
template<typename derived_t>
struct level2_impl : public level1_impl<derived_t>
{
auto only_for_level2_and_derived() const
{
return derived().bar;
};
};
struct level2 : public level2_impl<level2>
{
using level2_impl<level2>::level2_impl;
};
// ... and so on ...
一个人可以将其与最终类型一起使用,如下所示:
#include <iostream>
struct final : public level2_impl<final>
{
int foo = 1;
double bar = 2.0;
};
int main()
{
final f{};
std::cout<< f.only_for_level1_and_derived() <<std::endl; //prints variable foo = 1
std::cout<< f.only_for_level2_and_derived() <<std::endl; //prints variable bar = 2.0
}
或者,只需删除后缀_impl
,即可将每个级别用于其自身:
level1{}.do_something(); //prints "hi"
这是一件好事,特别是不能与该线程中的其他方法一起使用,例如
template<typename T> class A { auto& derived() {return static_cast<T&>(*this);} };
template<typename T> class B : A<B<T> > {};
template<typename T> class C : B<C> {}; //here derived() in the base class does
//not return C, but B<C> -- which is
//not what one usually wants in CRTP