Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are CRTP implementation and interface methods named differently?

Everywhere I read about CRTP and indeed in the code I write, a CTRP class hierarchy looks something like the following:

template< class T >
class Base
{

public:

    int foo_interface() 
    { 
        return static_cast< T* >(this)->foo_implementation();                        
    }

};

class Derived : public Base< Derived >
{

    friend class Base< Derived >;

    int foo_implementation() 
    { 
        return 5;
    }

};

That is, the name of the interface and implementing method is different. Now, I generally don't want the implementation methods to be visible from the outside, which necessitates the friend declaration above and in multi-level hierarchies turns out to be a major kludge (even with the trick described here).

Now, I came up with the following:

// Base class
template< class T >
class A
{

public:

    int foo() 
    {             
        std::cout << "I'm in A's foo!\n";
        return static_cast< T * >(this)->foo();            
    }

};

// Deriving class
class B : public A< B >
{

public:

    int foo()
    { 
        std::cout << "I'm in B's foo!\n";
        return 5; 
    }

};

// Deriving class with a nasty surprise...
class C: public A< C >
{

public:

    // ...crap, no foo to be found!

    int bar() 
    {             
        std::cout << "I'm in C's bar!\n";
        return 12; 
    }

};

template< class T >
int call_foo(A< T > & t)
{
    return t.foo();
}

B b;
C c;

Now, call_foo(b) works just like I would expect, calling B's implementation of foo(). Likewise, call_foo(c) also works as expected (in that it doesn't... it gets stuck in an infinite loop for obvious reasons). One drawback I can see is that if I forget to implement a method in the deriving class (or misspell something, forget to qualify it as const, whatever...), I get an infinite loop, so it might make bugs of that sort a bit harder to find since they're not caught at compile-time. Other than that, though, it's almost as simple as just plain virtual functions, and I don't have to fight with hiding the implementation methods. It seems easy and elegant, yet nobody seems to be using it... my question is, then, what's the catch? Why isn't this approach used? Is hiding the implementing methods simply not a big deal? Or is there some kind of immeasurably sinister evil force lurking in there ready to devour my soul the moment I try this approach on a real project?

like image 773
jaymmer - Reinstate Monica Avatar asked Jan 31 '13 12:01

jaymmer - Reinstate Monica


1 Answers

There's no "evil force" lurking behind the CRTP, apart maybe from the issue you mentioned with equally named interface functions and their implementations, but that's a bit like "asking for troubles" in my opinion.

As for the question "Why isn't this approach used?", I don't think that's the case. This approach is widely used when it is needed; but no design pattern makes sense all the time. Each comes handy in some particular design situation, and the CRTP is no exception.

The CRTP is mostly useful when you have several, unrelated classes that support a common interface but realize it slightly (not entirely) differently. If any of these words I've put in bold font does not describe your design use case, probably the CRTP makes no sense:

  • "Several": If you have only one such class instead of many, why factor out its interface into a superclass? It just introduces some redundant naming convention;
  • "Unrelated": If your classes are related, meaning that they derive from a common base class because run-time polymorphism is needed (this is very often the case, like if you want to have a heterogeneous collection of objects), then it often becomes natural to factor the common interface into that base class;
  • "Common": If your classes do not share a reasonably extensive common interface, there is not much to factor out into a base class. If all of your classes only have a size() method in common, for instance, creating a base class just to hold that method is likely a uselessly fine granularity of factoring;
  • "Slightly": If your interfaces are realized in a completely different way, meaning there is no common implementation, then it does not makes sense to create a base class that simply forwards all function calls to the subclasses. What for?

When your design situation, however, is such that all of the four properties above apply, then you definitely have a use case for the CRTP: no virtual function overhead, the compiler can fully optimize your code, you have a clean separation of interface and implementation, you achieve minimal redundancy by capturing the common implementation logic, and so on.

However, you might realize that this situation is just not that common. Hopefully this answers your question.

like image 198
Andy Prowl Avatar answered Sep 20 '22 17:09

Andy Prowl