2

In CRTP to avoid dynamic polymorphism, the following solution is proposed to avoid the overhead of virtual member functions and impose a specific interface:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo() {}; // required to compile. < Don't see why
};

struct your_type : base<your_type> {
  void foo() {}; // required to compile. < Don't see why
};

However it seems that the derived class does not require a definition to compile as it inherits one (the code compiles fine without defining a my_type::foo). In fact if a function is provided, the base function will not be called when using the derived class.

So the question is, is the following code replacement acceptable (and standard?):

template <class Derived>
struct base {
  void foo() {
    // Generate a meaningful error if called
    (void)sizeof( Derived::foo_IS_MISSING );
  };
};

struct my_type : base<my_type> {
  void foo() {}; // required to compile.
};

struct your_type : base<your_type> {
  void foo() {}; // required to compile.
};

int main() {
  my_type my_obj;
  my_obj.foo(); // will fail if foo missing in derived class
}
Community
  • 1
  • 1
effer
  • 21
  • 1
  • 3
  • 1
    What compiler and version? A simple test with g++ 4.2/4.6 compiles without adding the declaration in the derived type. Are you sure that you don't have a different issue that you are misinterpreting as related to that? Have you copied the exact code that fails? (BTW, the fact that it compiles does not mean that it is meaningful, without an implementation of `foo()` in the derived class, it will enter an infinite recursion loop which will end with a stackoverflow or an infinite loop if tail recursion optimization is performed. – David Rodríguez - dribeas Jul 20 '11 at 18:58
  • 1
    Oh, I almost forgot... Why do you think that you need to optimize the virtual dispatch? Have you actually measured the performance? Chances are that you are complicating the system for no particular reason. Before thinking in optimizing you should measure and make sure that you have a problem with performance, and that it is related to that piece of code, and that removing virtual dispatch will improve... and then, measure again, because you might still be wrong. – David Rodríguez - dribeas Jul 20 '11 at 19:00
  • 3
    Since you are calling (statically) `my_type::foo()` and `your_type::foo()` in `base::foo()`, it would seem sensible that you must have declared those member functions, otherwise you'd have a direct infinite recursion. – Kerrek SB Jul 20 '11 at 19:01
  • The idea here was to help the programmer define a derived class (typically a functor) that 'follows' a particular interface. It is different from the more common usage of dynamic polymorphism. Not sure it's a good idea though. – effer Jul 21 '11 at 07:33

4 Answers4

4

The whole point of this pattern is, as far as I understand, that you can pass arguments simply as template <typename T> base<T> & and your interface is defined by (non-virtual) functions in base<T>. If you don't have an interface that you want to define (as you are suggesting in the second part of your question), then there's no need for any of this in the first place.

Note that you are not "imposing" an interface like with pure virtual functions, but rather you are providing an interface. Since everything is resolved at compile time, "imposing" isn't such a strong requirement.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I think I misunderstood the main usage of this pattern, which is dynamic polymorphism whereas I was looking for a way to impose an interface without virtual functions. I wanted to help the programmer by providing explicitly an 'interface' that the derived class should adhere to. – effer Jul 21 '11 at 07:30
  • @effer You seem to have it the wrong way around. "dynamic polymorphism" is **not** the "main usage" of CRTP at all, since the two are completely orthogonal. The former requires virtuals (at least without a lot of hoop-jumping to reinvent the polymorphism wheel). But CRTP can be used to provide a basal interface, members, etc, yes. – underscore_d Apr 10 '16 at 22:39
2

In your replacement code you can't "polymorphically" call foo on a base<T>.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • I think I misunderstood the main usage of this pattern, which is dynamic polymorphism whereas I was looking for a way to impose an interface without virtual functions. – effer Jul 21 '11 at 07:28
2

However it seems that the derived class does not require a definition to compile as it inherits one (the code compiles fine without defining a my_type::foo).

C++ is lazy : it will not try to make base<my_type>::foo() if you do not actually use it. But if you try to use it, then it will be created and if that fails, compilation errors will flow. But in your case, base<my_type>::foo() can be instanciated just fine :

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {};

void func() {
    my_type m;
    static_cast<base<my_type>& >(m).foo();
}

will compile just fine. When the compiler is presented with static_cast(this)->foo(), it will try to find a foo() that is accessible in my_type. And there is one: it's called base<my_type>::foo(), which is public from a publicly inherited class. so base<my_type>::foo() calls base<my_type>::foo(), and you get an infinite recursion.

BatchyX
  • 4,986
  • 2
  • 18
  • 17
  • In the modified example (with sizeof()), the call to base::foo() should fail. I think I misunderstood the main usage of this pattern, which is dynamic polymorphism whereas I was looking for a way to impose an interface without virtual functions. – effer Jul 21 '11 at 07:28
0

No, imagine the following situation:

template <typename T>
void bar(base<T> obj) {
   obj.foo();
}

base<my_type> my_obj;
bar(my_obj);

Base's foo will be called instead of my_type's...

Do this, and you will get your erro message:

template <class Derived>
struct base {
  void foo() {
    sizeof(Derived::foo);
    static_cast<Derived *>(this)->foo();
  };
};

But I must confess I am not sure how this will work in compilers other than GCC, tested only with GCC.

André Puel
  • 8,741
  • 9
  • 52
  • 83