CRTP:基于派生类内容启用基类中的方法

时间:2017-02-28 16:33:58

标签: c++ crtp

有没有办法查询派生类'来自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"; }
};

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

1 个答案:

答案 0 :(得分: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上运行。

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

  

只是尝试在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上查看。