在一些之后,我提出了一个适用于模板派生类的私有成员的解决方案。它没有解决不将派生类的所有成员都暴露给基类的问题,因为它使用了friend
整个类的声明。另一方面,对于简单的情况,这不需要重复基本名称,也不需要模板参数,并且始终有效。
首先是派生非模板的简单情况。base 需要一个额外的void
模板参数,只是为了表明在 base 的额外模板参数的情况下一切仍然有效。根据 CRTP,唯一需要的是typename Derived
.
//Templated variadic base
template <typename Derived, typename...>
struct Interface
{
using CRTP = Interface; //Magic!
void f() { static_cast<Derived*>(this)->f(); }
};
//Simple usage of the base with extra types
//This can only be used when the derived is NON templated
class A : public Interface<A, void>
{
friend CRTP;
void f() {}
};
唯一需要的就是using CRTP = Interface;
基类中的friend CRTP;
声明和派生类中的声明。
对于派生本身是模板化的情况,情况会更加棘手。我花了一些时间来解决这个问题,我确信它仍然不完美。
大多数魔法发生在这些模板中:
namespace CRTP
{
template <template <typename, typename...> class _Base, typename _Derived, typename... _BaseArgs>
struct Friend { using Base = _Base<_Derived, _BaseArgs...>; };
template <template <typename, typename...> class _Base, typename ..._BaseArgs>
struct Base
{
template <template <typename...> class _Derived, typename... _DerivedArgs>
struct Derived : public _Base<_Derived<_DerivedArgs...>, _BaseArgs...> {};
};
}
它们的使用或多或少是直截了当的。两个使用上面的模板需要几个步骤。
首先,在派生类中继承时,需要给出从基类继承的可选参数。这是使用 来完成的CRTP::Base<MyBase, BaseOptional....>
,其中MyBase
是用于 CRTP 的类的名称,并且BaseOptional...
是在传递下一步提供的派生类之后直接传递给基类的模板参数。当基类不接受任何额外的模板参数时,它们可以完全省略:CRTP::Base<MyBase>
.
下一步是介绍派生类(CRTP 的重点)。这是通过在上面CRTP::Base<...>
加上::Derived<ThisDerived, DerivedOptional...>
. ThisDerived
这个定义的类在哪里,在这个类的声明中声明DerivedOptional...
的所有模板参数都在哪里template
。可选参数的指定与它们在类声明中出现的完全一样。template
最后一步是将基类声明为friend
. 这是通过friend typename CRTP::Friend<MyBase, ThisDerived, BaseOptional...>::Base
在类中声明某处来完成的。BaseOptional...
模板参数必须完全按照它们在CRTP::Base<MyBase, BaseOptional...>
继承自中出现的方式重复。
以下是在基不依赖于模板类型时使用模板派生的示例(但void
在此示例中它仍然可以采用其他模板参数)。
//Templated derived with extra, non-dependant types, passed to the base
//The arguments passed to CRTP::Base::Derived<, ARGS> must exactly match
// the template
template <typename T, typename... Args>
class B : public CRTP::Base<Interface, void>::Derived<B, T, Args...>
{
friend typename CRTP::Friend<Interface, B, void>::Base;
void f() {}
};
接下来是当基数依赖于派生的模板参数时的示例。与上一个示例的唯一区别是template
关键字。实验表明,如果为先前的非依赖案例指定了关键字,则代码也完全符合。
//Templated derived with extra dependant types passed to the base
//Notice the addition of the "template" keyword
template <typename... Args>
class C : public CRTP::Base<Interface, Args...>::template Derived<C, Args...>
{
friend typename CRTP::Friend<Interface, C, Args...>::Base;
void f() {}
};
请注意,这些模板不适用于非模板派生类。找到解决方案后,我将更新此答案,因此可以对所有情况使用统一的语法。可以做的最接近的事情就是使用一些假模板参数。请注意,它仍然必须命名并传递给 CRTP 机器。例如:
template <typename Fake = void>
class D : public CRTP::Base<Interface>::Derived<D, Fake>
{
friend typename CRTP::Friend<Interface, D>::Base;
void f() {}
};
注意A
, B
, C
&D
被声明为class
. 也就是说,它们的所有成员都是私有的。
以下是一些使用上述类的代码。
template <typename... Args>
void invoke(Interface<Args...> & base)
{
base.f();
}
int main(int, char *[])
{
{
A derived;
//Direct invocation through cast to base (derived.f() is private)
static_cast<A::CRTP &>(derived).f();
//Invocation through template function accepting the base
invoke(derived);
}
{
B<int> derived;
static_cast<B<int>::CRTP &>(derived).f();
invoke(derived);
}
{
C<void> derived;
static_cast<C<void>::CRTP &>(derived).f();
invoke(derived);
}
{
D<void> derived;
static_cast<D<>::CRTP &>(derived).f();
invoke(derived);
}
return 0;
}
invoke
独立的模板化函数适用于从基类派生的任何类。还显示了如何将派生转换为基,而无需实际指定基的名称。令人惊讶的是,这不依赖于任何系统头文件。
完整代码可在此处获得:https ://gist.github.com/equilibr/b27524468a0519aad37abc060cb8bc2b
欢迎评论和指正。