首页 文章

C静态多态(CRTP)并使用派生类中的typedef

提问于
浏览
53

我阅读了Wikipedia article关于C中用于执行静态(读取:编译时)多态的奇怪重复出现的模板模式 . 我想概括它,以便我可以根据派生类型更改函数的返回类型 . (这似乎应该是可能的,因为基类型知道模板参数中的派生类型) . 不幸的是,以下代码现在很容易访问gcc,所以我还没有尝试过 . )谁知道为什么?

template <typename derived_t>
class base {
public:
    typedef typename derived_t::value_type value_type;
    value_type foo() {
        return static_cast<derived_t*>(this)->foo();
    }
};

template <typename T>
class derived : public base<derived<T> > {
public:
    typedef T value_type;
    value_type foo() {
        return T(); //return some T object (assumes T is default constructable)
    }
};

int main() {
    derived<int> a;
}

顺便说一下,我有一个使用额外模板参数的解决方法,但我不喜欢它 - 当在继承链上传递许多类型时会变得非常冗长 .

template <typename derived_t, typename value_type>
class base { ... };

template <typename T>
class derived : public base<derived<T>,T> { ... };

EDIT:

MSVC 2010在这种情况下提供的错误消息是 error C2039: 'value_type' : is not a member of 'derived<T>'

g 4.1.2(通过codepad.org)说 error: no type named 'value_type' in 'class derived<int>'

5 回答

  • 55

    derived 在其基类列表中将其用作 base 的模板参数时不完整 .

    常见的解决方法是使用traits类模板 . 这是你的例子,traitsified . 这显示了如何通过特征使用派生类中的类型和函数 .

    // Declare a base_traits traits class template:
    template <typename derived_t> 
    struct base_traits;
    
    // Define the base class that uses the traits:
    template <typename derived_t> 
    struct base { 
        typedef typename base_traits<derived_t>::value_type value_type;
        value_type base_foo() {
            return base_traits<derived_t>::call_foo(static_cast<derived_t*>(this));
        }
    };
    
    // Define the derived class; it can use the traits too:
    template <typename T>
    struct derived : base<derived<T> > { 
        typedef typename base_traits<derived>::value_type value_type;
    
        value_type derived_foo() { 
            return value_type(); 
        }
    };
    
    // Declare and define a base_traits specialization for derived:
    template <typename T> 
    struct base_traits<derived<T> > {
        typedef T value_type;
    
        static value_type call_foo(derived<T>* x) { 
            return x->derived_foo(); 
        }
    };
    

    您只需要将 base_traits 专门用于 derived_tderived_t 的模板参数的任何类型,并确保每个专门化都提供 base 所需的所有成员 .

  • 6

    使用traits的一个小缺点是你必须为每个派生类声明一个 . 您可以编写一个不那么详细和重新划分的解决方法,如下所示:

    template <template <typename> class Derived, typename T>
    class base {
    public:
        typedef T value_type;
        value_type foo() {
            return static_cast<Derived<T>*>(this)->foo();
        }
    };
    
    template <typename T>
    class Derived : public base<Derived, T> {
    public:
        typedef T value_type;
        value_type foo() {
            return T(); //return some T object (assumes T is default constructable)
        }
    };
    
    int main() {
        Derived<int> a;
    }
    
  • 0

    在C 14中你可以删除 typedef 并使用函数 auto 返回类型扣除:

    template <typename derived_t>
    class base {
    public:
        auto foo() {
            return static_cast<derived_t*>(this)->foo();
        }
    };
    

    这是有效的,因为 base::foo 的返回类型的推断被延迟,直到 derived_t 完成 .

  • 8

    对需要较少样板文件的类型特征的替代方法是将派生类嵌套在包含typedef(或使用)的包装类中,并将包装器作为模板参数传递给基类 .

    template <typename Outer>
    struct base {
        using derived = typename Outer::derived;
        using value_type = typename Outer::value_type;
        value_type base_func(int x) {
            return static_cast<derived *>(this)->derived_func(x); 
        }
    };
    
    // outer holds our typedefs, derived does the rest
    template <typename T>
    struct outer {
        using value_type = T;
        struct derived : public base<outer> { // outer is now complete
            value_type derived_func(int x) { return 5 * x; }
        };
    };
    
    // If you want you can give it a better name
    template <typename T>
    using NicerName = typename outer<T>::derived;
    
    int main() {
        NicerName<long long> obj;
        return obj.base_func(5);
    }
    
  • 2

    我知道这基本上是你找到并且不喜欢的解决方法,但我想记录它,并且说它基本上是这个问题的当前解决方案 .

    我一直在寻找一种方法来做这件事,从来没有找到一个好的解决方案 . 事实上,这是不可能的,这就是为什么最终像 boost::iterator_facade<Self, different_type, value_type, ...> 这样的东西需要很多参数 .

    当然,我们希望这样的东西能起作用:

    template<class CRTP> 
    struct incrementable{
        void operator++(){static_cast<CRTP&>(*this).increment();}
        using ptr_type = typename CRTP::value_type*; // doesn't work, A is incomplete
    };
    
    template<class T>
    struct A : incrementable<A<T>>{
        void increment(){}
        using value_type = T;
        value_type f() const{return value_type{};}
    };
    
    int main(){A<double> a; ++a;}
    

    如果这是可能的,派生类的所有特征都可以隐式地传递给基类 . 我发现获得相同效果的成语是完全将特征传递给基类 .

    template<class CRTP, class ValueType> 
    struct incrementable{
        void operator++(){static_cast<CRTP&>(*this).increment();}
        using value_type = ValueType;
        using ptr_type = value_type*;
    };
    
    template<class T>
    struct A : incrementable<A<T>, T>{
        void increment(){}
        typename A::value_type f() const{return typename A::value_type{};}
    //    using value_type = typename A::value_type;
    //    value_type f() const{return value_type{};}
    };
    
    int main(){A<double> a; ++a;}
    

    https://godbolt.org/z/2G4w7d

    缺点是必须使用合格的 typenameusing 重新启用派生类中的特征 .

相关问题