2017-02-28 89 views
1

是否有方法从CRTP基类中查询派生类的内容,以便与SFINAE一起使用以启用或禁用基类方法?CRTP:基于派生类内容在基类中启用方法

我试图实现可能类似于以下内容:

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"; } 
}; 

无非是想在一个错误引用无效使用不完整的类的使用Derived::fooenable_if结果(衍生)。

回答

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上运行。

从上面的代码片段可以看到,基本思想是使用标签分派和重载方法来启用或禁用基类中的方法,并在派生类中使用该方法(如果存在的话)。

只是试图在内部使用Derived :: foo enable_if导致错误,指出对不完整类(派生)的无效使用。

这是因为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

+0

谢谢!我的C++现在有些生疏,所以不记得如何影响模板评估的顺序;多余的模板就是我所追求的。我同意你的解决方案比enable_if更有趣;仍然需要学习使用C++ 11及更高版本的东西。 –