C++ static polymorphism (CRTP) and using typedefs from derived classes
我读了维基百科文章中关于C++中奇怪的重复模板模式来做静态(读:编译时)多态性。我想推广它,以便能够基于派生类型更改函数的返回类型。(这似乎是可能的,因为基类型知道来自模板参数的派生类型)。不幸的是,下面的代码无法使用MSVC 2010进行编译(我现在无法轻松访问gcc,所以还没有尝试过)。有人知道为什么吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } |
顺便说一句,我有一项使用额外模板参数的工作,但我不喜欢它——当向继承链传递许多类型时,它会变得非常冗长。
1 2 3 4 5 | template <typename derived_t, typename value_type> class base { ... }; template <typename T> class derived : public base<derived<T>,T> { ... }; |
编辑:
在这种情况下,MSVC 2010给出的错误消息是
g++4.1.2(via codepad.org)说
当您将
一个常见的解决方法是使用特性类模板。这是你的例子,培训。这显示了如何通过特性同时使用派生类中的类型和函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // 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(); } }; |
您只需要专门化
使用特性的一个小缺点是,必须为每个派生类声明一个特性。您可以编写一个不那么冗长和冗长的解决方案,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } |
在C++ 14中,可以删除EDCOX1的6个参数,并使用函数EDCOX1×7的返回类型推导:
1 2 3 4 5 6 7 | template <typename derived_t> class base { public: auto foo() { return static_cast<derived_t*>(this)->foo(); } }; |
这是因为
对于需要较少样板的类型特征,另一种选择是将派生类嵌套在包含typedef(或使用)的包装类中,并将包装作为模板参数传递给基类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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); } |
我知道这基本上是你找到的解决方法,不喜欢,但是我想把它记录下来,并且说它基本上是这个问题的当前解决方案。
我一直在寻找一种方法来做到这一点有一段时间,从来没有找到一个好的解决方案。事实上,这是不可能的,这就是为什么最终像
当然,我们希望像这样的东西能起作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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;} |
如果可能的话,派生类的所有特性都可以隐式地传递给基类。我发现得到同样效果的习语是将特性完全传递给基类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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
缺点是派生类中的特性必须通过限定的