9

One of the main benefits of virtual in C++ is being able to use the base class (pointer or reference) to call derived methods.

I'm reading up on using CRTP to implement static polymorphism, but I can't understand how to achieve what I've mentioned above using this technique, because I can't declare a function as taking type Base when this requires a template.

It seems to me that what is described in the article could be achieved by simply using function overloading, so I'm sure that there must be more to this technique.

(PS: this exact problem is alluded to in a comment to an answer to this question, but unfortunately no one had replied to it: "What vtables truly provide is using the base class (pointer or reference) to call derived methods. You should show how it is done with CRTP here.")

Here is my minimal code, which gives the error "missing template arguments before ‘&’ token void Print(Base& Object)".

#include <cstring>
#include <iostream>

template <typename Derived>
struct Base
{
    std::string ToStringInterface() { return static_cast<Derived*>(this)->ToString(); }

    std::string ToString()  {   return "This is Base.";     }
};

struct Derived : Base<Derived>
{
    std::string ToString()  {   return "This is Derived.";  }
};

void Print(Base& Object)
{
    std::cout << Object->ToStringInterface() << std::endl;
}

int main()
{
    Derived MyDerived;

    // This works, but could have been achieved with a function overload.
    std::cout << MyDerived.ToStringInterface() << std::endl;

    // This does not work.
    Print(MyDerived);
}
Community
  • 1
  • 1
MGA
  • 1,658
  • 15
  • 28
  • *"because I can't declare a function as taking type Base when this requires a template."* The function `Print` must be a function template for static polymorphism, yes -- something like `template void Print(Base& Object);`. Static polymorphism is resolved at compile-time, so `Print` must know *exactly* which function to call *at compile-time*. – dyp Jun 10 '14 at 20:27

3 Answers3

6

Thanks to the comments and answers received, I'm posting my implementation, in case it may come in useful to anyone else.

#include <cstring>
#include <iostream>

template <typename Derived>
class Base
{
public:
    std::string ToStringInterface()
    {
        return static_cast<Derived*>(this)->ToString();
    }
};

template<>
class Base<void> : public Base<Base<void> >
{
public:
    std::string ToString()
    {
        return "This is Base (default implementation).";
    }
};

class Derived : public Base<Derived>
{
public:
    std::string ToString()
    { 
        return "This is Derived.";
    }
};

template <typename T>
void Print(Base<T>& Object)
{
    std::cout << Object.ToStringInterface() << std::endl;
}

int main()
{   
    int Decision;
    std::cout << "Do you want to create an object of type Base (input 0) or Derived (input 1)? ";
    std::cin >> Decision;
    if (Decision == 0)
    {
        Base<void> MyBase;
        Print(MyBase);
    }
    else
    {
        Derived MyDerived;
        Print(MyDerived);
    }
}
MGA
  • 1,658
  • 15
  • 28
5

Well, you need to declare print a template function :

template<class T>
void Print(Base<T>& Object)
{
    std::cout << Object.ToStringInterface() << std::endl;
}
Kiroxas
  • 891
  • 10
  • 24
  • Exactly what I needed. A small follow-up question: am I right that in this case I can't have a default method for Base, and can't instantiate objects of type Base? (which can be done with dynamic polymorphism). – MGA Jun 10 '14 at 20:33
  • 3
    You could create an instance of type `Base` that isn't actually a `Whatever`, but calling `ToStringInterface()` would invoke undefined behaviour. Make the ctor protected and/or check the correct type in `ToStringInterface()`. Note that checking with a `dynamic_cast` isn't always possible, because CRTP doesn't necessarily involve any virtual functions, but those are required for such a cast. – Ulrich Eckhardt Jun 10 '14 at 20:40
3

Sorry, but it CRTP indeed doesn't work that way. The idea is typically to inject some code into the dependency hierarchy, in a way that is very specific to C++. In your example, you could have e.g. an interface that requires the ToStringInterface() function and use CRTP to bind it to an existing class hierarchy's ToString():

class IStringable
{
    virtual string ToStringInterface() = 0;
};
class Unchangeable
{
    virtual string ToString();
};
template<class Derived>
class UnchangeableToIStringableMixin
{
    virtual string ToStringInterface()
    {
        return static_cast<Derived*>(this)->ToString();
    }
};
class StringableUnchangeable:
    public Unchangeable, UnchangeableToIStringableMixin<StringableUnchangeable>
{
};

However, if Unchangeable can actually be changed, you wouldn't do something like that. Don't forget to consider the possibility that CRTP just isn't the right tool for what you are doing.

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
  • Thanks. I was doing this more as an exercise in templates and to satisfy my curiosity, than anything else. In practice I can't see why I would go with all this complexity rather than just using `virtual`. Eventually I found a solution I liked by specializing the `Base` class with `void` (see below). – MGA Jun 10 '14 at 20:45
  • 1
    @Ulrich IMHO you don't need virtual on the member functions, even this is the main advantage of CRTP, kind of doing virtual at compile time, you will have a faster exec as it do not need the browse the virtual table at running time. – Jean Davy Jan 08 '15 at 16:15