CRTP:根据派生类内容启用基类中的方法
Posted
技术标签:
【中文标题】CRTP:根据派生类内容启用基类中的方法【英文标题】:CRTP: enable methods in base class based on derived class contents 【发布时间】:2017-02-28 16:33:58 【问题描述】:有没有办法从 CRTP 基类查询派生类的内容,与 SFINAE 一起使用以启用或禁用基类方法?
我想要完成的可能如下所示:
template<typename Derived>
struct base
struct foo ;
struct bar ;
void dispatch(int i)
switch (i)
case 0: dispatch(foo); break;
case 1: dispatch(bar); break;
default: break;
// catch all for disabled methods
template<typename T> void dispatch(T const&)
std::enable_if</* magic that checks if there is in fact Derived::foo(foo) */>
dispatch(foo f)
static_cast<Derived*>(this)->foo(f);
std::enable_if</* magic that checks if there is in fact Derived::bar(bar) */>
dispatch(bar b)
static_cast<Derived*>(this)->bar(b);
;
struct derived: public base<derived>
// only foo in this one
void foo(foo) std::cout << "foo()\n";
;
简单地尝试在enable_if
中使用Derived::foo
会导致错误引用不完整类(派生类)的无效使用。
【问题讨论】:
【参考方案1】:有没有办法从 CRTP 基类查询派生类的内容,与 SFINAE 一起使用以启用或禁用基类方法?
是的,是的。它遵循一个最小的工作示例:
#include<iostream>
template<typename D>
class base
template<typename T = D>
auto dispatch(int) -> decltype(std::declval<T>().foo(), void())
static_cast<T*>(this)->foo();
void dispatch(char)
std::cout << "base" << std::endl;
public:
void dispatch()
dispatch(0);
;
struct derived1: base<derived1>
void foo()
std::cout << "derived1" << std::endl;
;
struct derived2: base<derived2> ;
int main()
derived1 d1;
derived2 d2;
d1.dispatch();
d2.dispatch();
添加要转发的参数很简单,我希望示例尽可能简单。 查看它在wandbox 上运行。
从上面的sn-p可以看出,其基本思路是使用标签调度和重载方法来启用或禁用基类中的方法,如果存在则使用派生类中的方法。
仅尝试在 enable_if 中使用 Derived::foo 会导致错误,指出对不完整类(派生)的使用无效。
那是因为Derived
在您尝试使用它时实际上是不完整的。标准说:
在类说明符的结尾处,类被视为完全定义的对象类型(或完整类型)。
在您的情况下,派生类有一个基类模板,而前者在后者的实例化过程中不是一个完整的类型,原因很明显。
此外,Derived
不是您的 sfinae 表达式中的实际类型,并且(让我说)在这种情况下 sfinae 不起作用。这就是我在示例中执行以下操作的原因:
template<typename T = D>
auto dispatch(int) -> decltype(std::declval<T>().foo(), void())
static_cast<T*>(this)->foo();
当然,decltype
这样使用也是一个 sfinae 表达式。如果您愿意,可以使用std::enable_if_t
做类似的事情。我发现这个版本更容易阅读和理解。
话虽如此,您可以通过虚拟方法获得相同的结果。如果您没有充分的理由不这样做,请使用它。
为了完整起见,您的示例已更新为上述技术:
#include<iostream>
template<typename Derived>
struct base
struct foo ;
struct bar ;
void dispatch(int i)
switch (i)
case 0: dispatch(0, foo); break;
case 1: dispatch(0, bar); break;
default: break;
template<typename T>
void dispatch(char, T const&)
template<typename D = Derived>
auto dispatch(int, foo f)
-> decltype(std::declval<D>().foo(f), void())
static_cast<D*>(this)->foo(f);
template<typename D = Derived>
auto dispatch(int, bar b)
-> decltype(std::declval<D>().bar(b), void())
static_cast<D*>(this)->bar(b);
;
struct derived: public base<derived>
void foo(foo) std::cout << "foo" << std::endl;
;
int main()
derived d;
d.dispatch(0);
d.dispatch(1);
在wandbox 上查看。
【讨论】:
谢谢!这几天我的C++有点生疏了,所以不记得怎么影响模板评估顺序了;额外的模板以上是关于CRTP:根据派生类内容启用基类中的方法的主要内容,如果未能解决你的问题,请参考以下文章
为啥基类指针指向基类中的纯虚方法,而不是派生类中的覆盖方法?