0

Please help me solve this problem. WhiteDragon is to call Dragon::attacks() instead of MonsterImplement::attacks(), and there is ambiguity error here. If I change Dragon to be derived from MonsterImplement, then the line std::cout << monster->numAttacks << std::endl; won't compile because Dragon has no numAttacks data member (nor should it, because different types of Dragons are to have different values). So I need WhiteDragon to call Dragon::attacks() and to call finalizeMonster() during its instantiation. If I make Dragon virtual derived class of Monster, WhiteDragon calls up MonsterImplement::attacks() instead.

#include <iostream>

struct Monster {
    virtual void finalizeMonster() {}
    virtual void attack() {}
};

template <class MONSTER, int NUM>
struct MonsterInt: virtual public Monster {
    static int numAttacks;
   };

template <class MONSTER, int NUM>
int MonsterInt<MONSTER, NUM>::numAttacks = NUM;

template <class BASE, class MONSTER>
struct MonsterImplement: virtual public BASE {
    MonsterImplement() {finalizeMonster();}
    virtual void finalizeMonster() override;
    virtual void attack() override {std::cout << "MonsterImplement::attack()" << std::endl;}
};

struct Dragon: public Monster {  // or Dragon: public MonsterImplement<Monster, Dragon> ?
// but then Dragon will also call the MonsterImplement constructor (when it has no numAttacks member)
    virtual void attack() override {std::cout << "Dragon::attack()" << std::endl;}
};

struct WhiteDragon: public MonsterInt<WhiteDragon, 3>, 
    public MonsterImplement<Dragon, WhiteDragon> {
    WhiteDragon(): MonsterImplement<Dragon, WhiteDragon>() {}
};

template <class BASE, class MONSTER>
inline void MonsterImplement<BASE, MONSTER>::finalizeMonster() {
    MONSTER* monster = static_cast<MONSTER*> (this);
    std::cout << monster->numAttacks << std::endl;
}

int main() {
    WhiteDragon wd;
    wd.attack();
}
prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • I'm confused why you have templates everywhere here. It seems like most of the things you're trying to achieve with it could be done with simple polymorphism. But maybe I'm wrong. – aruisdante Mar 14 '14 at 04:00
  • My entire code has many static data members, so I wanted to use templates (and multiple inheritance) to establish the static data members for all the Monster subtypes (numAttacks is one such, and illustrated above). I could simplify the above to get what I want to achieve here, but then the cost would be that all identical monsters will have common values that are not static anymore. The problem here is that the CRTP is not single-level. – prestokeys Mar 14 '14 at 04:04
  • 1
    @aruisdante, maybe you've never seen [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). I haven't studied this example enough to know if it's being used correctly here. – Mark Ransom Mar 14 '14 at 04:04
  • I mean you can absolutely have shared static data members (that's the whole point of the `static` keyword inside a class). CRTP seems to be used mostly to avoid the performance penalty of virtual function table lookups by essentially translating it to psudo static binding. But as with @MarkRansom, definitely don't know it well enough to determine if it's being used properly. – aruisdante Mar 14 '14 at 04:13
  • 1
    The middle section of this page: http://katyscode.wordpress.com/2013/08/22/c-polymorphic-cloning-and-the-crtp-curiously-recurring-template-pattern/ (after the subtitle: CRTP with Multi-Level Inheritance) is the pattern I'm trying to follow to handle the multi-level CRTP issue. – prestokeys Mar 14 '14 at 04:19
  • CRTP is meant to provide non-dynamic behavior. If the value of "numAttacks" vary with each derived class, this is not a "non-dynamic" situation. A counter-example would be to put a non-static non-virtual method `int numAttacks() { return 3; }` in a derived class, and then in the CRTP base class add some methods (the attack logic that is shared across all derived classes), which can then call the `numAttacks()` method on its derived class, without incurring a virtual function call. – rwong Mar 14 '14 at 05:23

2 Answers2

3

(Copied from an earlier comment.)

Perspective #1

CRTP is meant to provide non-dynamic behavior. If the value of "numAttacks" vary with each derived class, this is not a "non-dynamic" situation. A counter-example would be to put a non-static non-virtual method int numAttacks() { return 3; } in a derived class, and then in the CRTP base class add some methods (the attack logic that is shared across all derived classes), which can then call the numAttacks() method on its derived class, without incurring a virtual function call.

Example:

struct Monster
{
    virtual void attack() = 0;
    virtual int getNumAttacks() const = 0;
};

template <struct MONSTER>
struct AttackLogic : virtual public Monster
{
    virtual void attack() override
    {
        /* allowed to call MONSTER::getNumAttacks(), renamed to avoid confusion. */

        int numAttacks = static_cast<MONSTER*>(this).getNumAttacks();

        /* Use the value in attack calculations. */
    }
};

struct Unicorn 
    : virtual public Monster
    , virtual public AttackLogic<Unicorn>
{
    virtual int getNumAttacks() const override
    {
        return 42; // Unicorn is awesome
    }
};

Disclaimer: Code only meant to explain my suggestion. Not intended for practical use. Not tested with compiler. My knowledge of virutal inheritance is weak, so there may be mistakes or broken guidelines in the sample code above.


Your current inheritance chain is: (base at top)

  • Monster
    • Dragon
      • MonsterImplement<Dragon, WhiteDragon>
        • WhiteDragon

Monster defines:

  • virtual finalizeMonster() // abstract
  • virtual attack() // abstract

Dragon defines:

  • virtual attack() // concrete, overrides Monster.attack()

MonsterImplement<...> defines:

  • virtual attack() // concrete, overrides Dragon.attack() and Monster.attack()

WhiteDragon defines:

  • (no new virtual methods defined)

It is very clear that "after fixing the bug", that MonsterImplement.attack() will be called, because it is a subclass of Dragon and therefore overrides it.

In general it only says that the current inheritance hierarchy is badly designed, and that nobody would be able to fix it.


Perspective #2

Injecting a static int through CRTP pattern is rarely worth the effort. CRTP is more suitable for injecting a set of non-static, non-virtual methods ("boilerplate") in a way that will not be overridden, that saves every derived class from re-implementing the same "boilerplate".

At the minimum, convert the static int numAttacks into a virtual function

virtual int numAttacks() const { throw std::exception(); }

or

virtual int numAttacks() const = 0;  // abstract

and then provide a concrete implementation in WhiteDragon to return 3.

struct WhiteDragon : ... 
{   ... 
    virtual int numAttacks() const override { return 3; } 
    ... 
};

rwong
  • 6,062
  • 1
  • 23
  • 51
  • "MonsterImplement.attack() will be called, because it is a subclass of Dragon." I'm open to changing what is subclass of what if that will solve the problem. I used to have Dragon below MonsterImplement before (and also the diamond pattern with them). I had considered using virtual functions for ALL the many monster attributes, but felt it would be too much, and that a list of template parameters for static data would look much nicer. And it is too late to change now anyway. – prestokeys Mar 14 '14 at 05:33
  • @prestokeys There is already a heated debate on the advice "prefer composition over inheritance". The origin of that phrase is that in any complicated system, one can never find the right inheritance hierarchy to make it work correctly. It is so heated and hated that I wouldn't be providing a URL - just search on StackOverflow and Programmers.StackExchange, and feel the heat. This debate is limited to C++ only because of multiple inheritance (from multiple concrete implementations). C# and Java have interfaces (pure virtual declarations) and are less prone to this issue. – rwong Mar 14 '14 at 05:38
0
template <class MONSTER, int NUM>
struct MonsterInt: virtual public Monster {
    static int numAttacks;
   };

What's the purpose of this class? It seems like all it does is give a class a number of attacks, in which case it doesn't really make sense to derive from monster.

template <int NUM>
struct MonsterInt {
    static int numAttacks;
   };

That 'fixes' the program I think, but it's hard to really say because intention is hard to derive from your code.

QuestionC
  • 10,006
  • 4
  • 26
  • 44
  • I cannot it change it to that. There would be too much to show from my code to explain why. But I've already found a solution using the diamond formation. – prestokeys Mar 16 '14 at 04:46