73

I read the Wikipedia article about the curiously recurring template pattern in C++ for doing static (read: compile-time) polymorphism. I wanted to generalize it so that I could change the return types of the functions based on the derived type. (This seems like it should be possible since the base type knows the derived type from the template parameter). Unfortunately, the following code won't compile using MSVC 2010 (I don't have easy access to gcc right now so I haven't tried it yet). 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;
}

BTW, I have a work-around using extra template parameters, but I don't like it---it will get very verbose when passing many types up 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>'

T.C.
  • 123,516
  • 14
  • 264
  • 384
Samuel Powell
  • 981
  • 1
  • 8
  • 8

5 Answers5

74

derived is incomplete when you use it as a template argument to base in its base classes list.

A common workaround is to use a traits class template. Here's your example, traitsified. This shows how you can use both types and functions from the derived class through the 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 template argument derived_t of base and make sure that each specialization provides all of the members that base requires.

James McNellis
  • 327,682
  • 71
  • 882
  • 954
  • I created one test file and compiled code of OP with putting a main() with Ubuntu g++ 4.4.1 It works fine. Declaring an object like `Derived` gives error, I am trying to figure out why ? – iammilind May 15 '11 at 05:53
  • 2
    @iammilind That's because in an empty `main()` no template instantiation takes place. Certain errors only come up when the compiler tries to instantiate and use the template. – greatwolf May 15 '11 at 05:59
  • 1
    Right; you have to instantiate the template for there to be any errors here; if you add `int main() { derived().base_foo(); }` to the bottom of my traits example (this forces instantiation of everything), it should compile with Visual C++ 2010, g++ 4.5.1, and the latest Clang builds. – James McNellis May 15 '11 at 06:04
  • @both, agreed. However, I would prefer to switch the arguments and make `derived` a default argument (this approach will not demand much code). I have posted in my answer. – iammilind May 15 '11 at 06:30
  • 2
    could you explain more about what you mean by this "derived is incomplete when you use it as a template argument to base in its base classes list". what way is it incomplete? – Sriram Subramanian May 15 '11 at 06:31
  • 1
    @JamesMcNellis and Sriram, I sense a confusion is possible. Slightly flattened version with derived not being a template: `struct derived : base {...};` does make the base_traits not defined. – mloskot Nov 15 '11 at 13:18
11

In C++14 you could remove the typedef and use function auto return type deduction:

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

This works because the deduction of the return type of base::foo is delayed until derived_t is complete.

Oktalist
  • 13,098
  • 1
  • 38
  • 56
  • 2
    Does this technique limits to return value? Does it work on a field, or function parameter? Will C++17 help even more? – cppBeginner Nov 19 '17 at 05:16
10

One small drawback of using traits is that you have to declare one for each derived class. You can write a less verbose and redondant 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;
}
matovitch
  • 1,172
  • 10
  • 21
  • what is the trait here? – sehe Jan 09 '15 at 14:32
  • 1
    This looks interesting, but I don't really understand it, could you explain it a bit more? – Ben Farmer Feb 19 '16 at 10:42
  • @BenFarmer Instead of relying on the derived type to extract the value_type in the base, we directly pass it to the base class. The base class now has two template parameters. This is very practical if you have only or two types to pass, but if you have, this is not convenient. – Baptiste Wicht Aug 12 '16 at 08:54
  • @BaptisteWicht Indeed, you could use variadic templates and tuples for a bit more flexibility. – matovitch Aug 12 '16 at 11:48
  • It is not redundant as you don't pass the type of the derived class but its template as template argument of the base class. (Note one can remove the typedef in the derived class) – matovitch Aug 21 '16 at 15:18
  • @matovitch Yeah, I deleted my comment as I realised this wasn't doing what I thought. And being a template, in approximately 3 hours I'll have figured out what it _is_ doing. ;-) – underscore_d Aug 21 '16 at 15:20
3

An alternative to type traits that requires less boilerplate is to nest your derived class inside a wrapper class that holds your typedefs (or using's) and pass the wrapper 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);
}
Alkis
  • 121
  • 1
  • 5
  • Clever, but this has side effects. For example, `T` will not be able to be deduced in function tamplates, for example `template f(outer::derived x)` (as opossed to `template f(derived2 x)`. – alfC Jan 16 '19 at 05:39
  • You can write `template auto f(C x)`. If you're worried about accepting arbitrary types you can add `class = std::enable_if_t< std::is_same_v< C, NicerName >, int>` to the template parameters list but it gets quite long. – Alkis Jan 19 '19 at 16:23
0

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

I have been looking for a way to do this for a while and never found a good solution. The fact that it is not possible is the reason why ultimately, things like boost::iterator_facade<Self, different_type, value_type, ...> need many parameters.

Of course we would like something 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 this was possible, all the traits of the derived class could be passed implicitly ot the base class. The idiom I found to get the same effect is to pass the traits to the base class entirely.

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 drawback is that the trait in the derived class has to be accessed with a qualified typename or reenabled by using.

alfC
  • 10,293
  • 4
  • 42
  • 88