【问题标题】:CRTP: enable methods in base class based on derived class contentsCRTP:根据派生类内容启用基类中的方法
【发布时间】: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 会导致错误引用不完整类(派生类)的无效使用。

【问题讨论】:

    标签: c++ crtp


    【解决方案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++有点生疏了,所以不记得怎么影响模板评估顺序了;额外的模板 是我所追求的。我同意与 enable_if 相比,您的解决方案更出色;从 C++11 及更高版本开始学习使用的东西。
    猜你喜欢
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    • 2019-07-25
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多