C ++ Static Polymorphism (CRTP) and Using Typedefs from Derived Classes

I read a Wikipedia article on a curiously repeating pattern template in C ++ for performing static (read: compile-time) polymorphism. I wanted to generalize it so that I could change the return types of functions based on a derived type. (This seems to be possible, since the base type knows the derived type from the template parameter). Unfortunately, the following code will not compile using MSVC 2010 (I don't have easy access to gcc right now, so I haven't tried it yet). Does anyone know why?

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

By the way, I have a workaround using additional template parameters, but I don't like it --- it will be very verbose when passing many types to the inheritance chain.

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

EDIT:

The error message that MSVC 2010 gives in this situation is error C2039: 'value_type' : is not a member of 'derived<T>'

g ++ 4.1.2 (via codepad.org ) says error: no type named 'value_type' in 'class derived<int>'

+49
c ++ inheritance typedef templates crtp
May 15 '11 at 5:09 a.m.
source share
6 answers

derived is incomplete if you use it as a template argument for base in its list of base classes.

A common workaround is to use the feature class template. Here is your touching example. This shows how you can use both types and functions from a derived class through traits.

 // 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(); } }; 

You just need to specialize base_traits for any types that you use for the derived_t base template argument, and make sure that each specialization provides all the members that base requires.

+51
May 15 '11 at 5:16
source share

One small drawback of using traits is that you must declare one for each derived class. You can write a less detailed and repeated workaround like this:

 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; } 
+8
Jan 09 '15 at 14:24
source share

In C ++ 14, you can remove the typedef and use the auto function to type the return type:

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

This works because the output of the return type base::foo delayed until derived_t completes.

+6
Aug 12 '16 at 13:30
source share

An alternative to a type of type that requires less template is nesting your derived class inside a wrapper class that contains your typedef (or use) and passes the shell as a template argument to your base class.

 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
Nov 13 '17 at 12:23
source share

I know that this is basically a workaround that you found that you don't like, but I wanted to document it, and also say that this is basically the current solution to this problem.

I have been looking for a way to do this for a long time and have not found a good solution. The fact that this is not possible is the reason that in the end, things like boost::iterator_facade<Self, different_type, value_type,...> require a lot of parameters.

Of course, we would like something like this to work:

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

If it were possible, all the features of the derived class could be passed implicitly from the base class. The idiom I found to get the same effect is to pass the traits to a fully base class.

 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

The disadvantage is that the attribute in the derived class must be accessed with qualified typename or reused using with using .

0
Jan 16 '19 at 7:04
source share

You can avoid passing 2 arguments to template . In CRTP, if you have confidence that the class base will be paired with class derived (and not with class derived_2 ), use the method below:

 template <typename T> class derived; // forward declare template <typename value_type, typename derived_t = derived<value_type> > class base { // make class derived as default argument value_type foo(); }; 

Using:

 template <typename T> class derived : public base<T> // directly use <T> for base 
-four
May 15 '11 at 6:19 06:19
source share



All Articles