Any trick to inherit static data members?

How to simplify the following? In a dream world I would like to omit all the (many) static data member declarations in the (hundreds of) derived classes of Monster, but static data members cannot be inherited.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>

struct Monster {
	static int hitDice, numberOfAttacks, intensityOfBadBreath; // etc...
};
int Monster::hitDice;
int Monster::numberOfAttacks;
int Monster::intensityOfBadBreath;  // etc...

struct Goblin: Monster {
	static int hitDice, numberOfAttacks, intensityOfBadBreath;  // etc...
	Goblin() {hitDice = 1; numberOfAttacks = 1; intensityOfBadBreath = 8;}  // etc...
};
int Goblin::hitDice;
int Goblin::numberOfAttacks;
int Goblin::intensityOfBadBreath;  // etc...

struct Dragon: Monster {
	static int hitDice, numberOfAttacks, intensityOfBadBreath;  // etc...
	Dragon() {hitDice = 20; numberOfAttacks = 3; intensityOfBadBreath = 9;}  // etc...
};
int Dragon::hitDice;
int Dragon::numberOfAttacks;
int Dragon::intensityOfBadBreath;  // etc...

struct Troll: Monster {
	static int hitDice, numberOfAttacks, intensityOfBadBreath;  // etc...
	Troll() {hitDice = 8; numberOfAttacks = 3; intensityOfBadBreath = 10;}  // etc...
};
int Troll::hitDice;
int Troll::numberOfAttacks;
int Troll::intensityOfBadBreath;  // etc...

// etc... for hundreds of Monster subtypes

int main() {
	Goblin goblinDude;
	Dragon redDragon;
	Troll slimeyTroll;
	std::cout << "Goblin::hitDice = " << Goblin::hitDice << std::endl;
	std::cout << "Dragon::hitDice = " << Dragon::hitDice << std::endl;
	std::cout << "Troll::hitDice = " << Troll::hitDice << std::endl;
	// etc...
}


Or should I just forget about making them static and thus get the inheritance I want? If not static, would it be acceptable style to call medusa->numberOfAttacks, when all Medusas have the same number of attacks? Is the waste of memory storage acceptable? (all monsters are deleted once they are killed)
Last edited on
> (hundreds of) derived classes of Monster

Hundreds? Or even dozens?
Consider using the type object pattern. http://gameprogrammingpatterns.com/type-object.html



Back to the original problem: static data members cannot be inherited

Use an intermediate (templated) class which colours the static member variables. Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <memory>
#include <utility>
#include <cassert>

struct monster {
    virtual ~monster() = default ;

    virtual int attacks() const = 0 ;
	// etc.
};

template < typename DERIVED, int NATTACKS > struct monster_type : monster {
    static int numberOfAttacks ;
    virtual int attacks() const override { return numberOfAttacks ; }
    // etc.
};

template < typename DERIVED, int NATTACKS >
int monster_type<DERIVED,NATTACKS>::numberOfAttacks = NATTACKS ;
// etc.

struct goblin: monster_type<goblin,5> {};
struct dragon: monster_type<dragon,20> { dragon(double,int) {} };
struct troll: monster_type<troll,8> { explicit troll(int) {} };

namespace cpp14
{
    template< class T, typename... ARGS >
    std::unique_ptr<T> make_unique( ARGS&&... args )
    { return std::unique_ptr<T>( new T( std::forward<ARGS>(args)... ) ) ; }
}

int main()
{
    using cpp14::make_unique ;
    std::unique_ptr<monster> monsters[] = {
        make_unique<goblin>(), make_unique<dragon>( 3.4, 7 ), make_unique<troll>(24) } ;

   for( const auto& pointer : monsters ) std::cout << pointer->attacks() << ' ' ;
   assert( dragon::numberOfAttacks == 20 && "something is wrong" ) ;
}

http://coliru.stacked-crooked.com/a/a82d46c292ed6c57
What if some of the static members are class types? The following works, but is it the right way to do it?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <memory>
#include <utility>
#include <list>

struct MonsterHitDice {
	double level;
	int adjustment;
	int specialAbilitiesBonus;
};

struct Monster {
    virtual ~Monster() = default ;

	virtual MonsterHitDice HitDice() const = 0;
	virtual int ArmorClass() const = 0;
    virtual int NumberOfAttacks() const = 0;
	// etc.
};

template <typename DERIVED, typename MONSTERHD, const MONSTERHD& HITDICE, int AC, int NATTACKS> 
struct MonsterType: Monster {
	static MonsterHitDice hitDice;
    static int armorClass, numberOfAttacks;
    virtual MonsterHitDice HitDice() const override {return hitDice;}
    virtual int ArmorClass() const override {return armorClass;}
    virtual int NumberOfAttacks() const override {return numberOfAttacks ;}
    // etc.
};

template <typename DERIVED, typename MONSTERHD, const MONSTERHD& HITDICE, int AC, int NATTACKS> 
MonsterHitDice MonsterType<DERIVED, MONSTERHD, HITDICE, AC, NATTACKS>::hitDice = HITDICE;

template <typename DERIVED, typename MONSTERHD, const MONSTERHD& HITDICE, int AC, int NATTACKS> 
int MonsterType<DERIVED, MONSTERHD, HITDICE, AC, NATTACKS>::armorClass = AC;

template <typename DERIVED, typename MONSTERHD, const MONSTERHD& HITDICE, int AC, int NATTACKS> 
int MonsterType<DERIVED, MONSTERHD, HITDICE, AC, NATTACKS>::numberOfAttacks = NATTACKS;
//// etc.

MonsterHitDice goblin_hd = {1,-1}, dragon_hd = {20,1}, troll_hd = {8,0,2};  // How to do it without resorting to this?

struct Goblin: public MonsterType<Goblin, MonsterHitDice, goblin_hd, 6, 1> {};
struct Dragon: MonsterType<Dragon, MonsterHitDice, dragon_hd, -2, 3> {};
struct Troll: MonsterType<Troll, MonsterHitDice, troll_hd, 2, 3> {};

int main() {
   	std::list<Monster*> monsters = {new Goblin(), new Dragon(), new Troll()};
	for (const auto& x: monsters)
		std::cout << x->HitDice().level << ", " << x->HitDice().adjustment <<
 ", " << x->ArmorClass() << ", " << x->NumberOfAttacks() << std::endl;	std::cout << Goblin::hitDice.level << ", " << Goblin::hitDice.adjustment
 << ", " << Goblin::armorClass << ", " << Goblin::numberOfAttacks << std::endl;
} 

Output:
1
2
3
4
1, -1, 6, 1
20, 1, -2, 3
8, 0, 2, 3
1, -1, 6, 1


The initialization
MonsterHitDice goblin_hd = {1,-1}, dragon_hd = {20, 1}, troll_hd = {8,0,2};
is certainly awkward (and global too), but lvalues are needed as template parameters.
Last edited on
> What if some of the static members are class types?

More of the same (if the class types can be initialised with template non-type parameters)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <memory>
#include <utility>
#include <list>

struct MonsterHitDice {
    double level;
	int adjustment;
	int specialAbilitiesBonus;
};

struct Monster {
    virtual ~Monster() = default ;

	virtual MonsterHitDice HitDice() const = 0;
	virtual int ArmorClass() const = 0;
    virtual int NumberOfAttacks() const = 0;
	// etc.
};

template <typename DERIVED, int AC, int NATTACKS>
struct MonsterType: virtual Monster {
    static int armorClass, numberOfAttacks;
    virtual int ArmorClass() const override {return armorClass;}
    virtual int NumberOfAttacks() const override {return numberOfAttacks ;}
    // etc.
};

template <typename DERIVED, int AC, int NATTACKS>
int MonsterType<DERIVED, AC, NATTACKS>::armorClass = AC;

template <typename DERIVED, int AC, int NATTACKS>
int MonsterType<DERIVED, AC, NATTACKS>::numberOfAttacks = NATTACKS;
//// etc.

template < typename DERIVED, int ADJ, int BONUS, int LEVEL_NUM, int LEVEL_DEN = 1  >
struct HitDeviceType : virtual Monster {
	static MonsterHitDice hitDice;
	virtual MonsterHitDice HitDice() const override {return hitDice;}
};

template < typename DERIVED, int ADJ, int BONUS, int LEVEL_NUM, int LEVEL_DEN >
MonsterHitDice HitDeviceType< DERIVED, ADJ, BONUS, LEVEL_NUM, LEVEL_DEN >::hitDice =
{ double(LEVEL_NUM)/LEVEL_DEN, ADJ, BONUS } ;

struct Goblin: MonsterType<Goblin, 6, 1>, HitDeviceType<Goblin,-1,0,1>  {};
struct Dragon: MonsterType<Dragon, -2, 3>, HitDeviceType<Dragon,20,0,1>  {};
struct Troll: MonsterType<Troll, 2, 3>, HitDeviceType<Troll,0,2,8> {};

int main() {
    std::list<Monster*> monsters = {new Goblin(), new Dragon(), new Troll()};
    for (const auto& x: monsters)
	std::cout << x->HitDice().level << ", " << x->HitDice().adjustment << ", " 
                  << x->ArmorClass()  << ", " << x->NumberOfAttacks() << std::endl;	
        
    std::cout << Goblin::hitDice.level << ", " << Goblin::hitDice.adjustment
              << ", " << Goblin::armorClass << ", " << Goblin::numberOfAttacks << std::endl;
}

http://coliru.stacked-crooked.com/a/ad87f4c53e2a1a28
So let's say class Goblin has several different base classes
Goblin: A<....>, B<...>, C<...>, ...
(themselves all derived from Monster). Suppose we now want to define void Monster::attacks(), which is pure virtual in Monster (which has no data members). If attacks() is to use a data member from A, then simply define virtual void A::attacks() override;. No problem so far. But what if attacks() uses data members from A, B, and C? How do we override it then? A has no knowledge of the data members of B or C. If we make A, B, C share those data members, some of them will be unitialized because their template parameters did not initialize them. How to solve that problem. Is it to just stick with one base class for Goblin (and all the Monster subtypes), i.e. do as originally planned albeit messy and with global variables? This is what is looks like currently with the original plan
1
2
3
4
5
6
7
8
9
10
11
12
MonsterHitDice GoblinHD = {1,-1}, OwlBearHD = {5,0};
DamageType GoblinDamage = {{1,6}}, OwlBearDamage = {{1,8},{1,8},{1,8}};
SaveAs GoblinSave = {SaveAsNormalMan}, OwlBearSave = {SaveAsFighter, 3};
SpeedPerRound GoblinSpeed = {20, 60}, OwlBearSpeed = {40, 100, 150};
NumberOfMonsters GoblinNum = {{2,8}, {6,60}}, OwlBearNum = {{1,4},{1,4}};
TreasureTypesOfMonster GoblinTreasure = {TreasureTypeR, TreasureTypeC},
    OwlBearTreasure = {TreasureTypeC};
BoolStats<NumMonsterBoolValues> GoblinBoolStats = {DemiHumanTrue},
    OwlBearBoolStats = {OgreSizeOrBiggerTrue, TooBigForWebSpell};

class Goblin: public MonsterType<Goblin, GoblinHD, 6, 1, GoblinDamage, GoblinNum,
7, GoblinSave, GoblinSpeed, GoblinTreasure, Chaotic, GoblinBoolStats> {...}

and one of the 12 static data member initializations:
1
2
3
4
5
6
template <class DERIVED, const MonsterHitDice& HIT_DICE, int ARMOR_CLASS, int NUM_ATTACKS, 
  const DamageType& DAMAGE, const NumberOfMonsters& NUM_APPEARING, int MORALE, const SaveAs& SAVE_AS, 
  const SpeedPerRound& SPEED, const TreasureTypesOfMonster& TREASURE, AlignmentType ALIGNMENT, 
  const BoolStats<NumMonsterBoolValues>& BOOL>
MonsterHitDice MonsterType<DERIVED, HIT_DICE, ARMOR_CLASS, NUM_ATTACKS, DAMAGE, NUM_APPEARING, 
  MORALE, SAVE_AS, SPEED, TREASURE, ALIGNMENT, BOOL>::hitDice = HIT_DICE;

I can't even arrange the above global variables in map or array form because then it would not be usable in a constant expression. But listing out all the parameters to fill in for those classes (using template-template arguments or not) will make the template list waaaaay too long, which is probably worse in terms of readability.
Last edited on
> But what if attacks() uses data members from A, B, and C?
> How do we override it then?

CRTP http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

struct monster {
    virtual ~monster() = default ;

    virtual int foo() const = 0 ;
    virtual int bar() const = 0 ;
    virtual int baz() const = 0 ;

    virtual int foo_bar() const = 0 ;
    virtual int foo_bar_baz() const = 0 ;
};

template < typename DERIVED, int FOO = 0 > struct foo_impl : virtual monster {
    static constexpr int foo_val = FOO ;
    virtual int foo() const override { return foo_val ; }
};

template < typename DERIVED, int BAR = 1 > struct bar_impl : virtual monster {
    static constexpr int bar_val = BAR ;
    virtual int bar() const override { return bar_val ; }
};

template < typename DERIVED, int BAZ = 2 > struct baz_impl : virtual monster {
    static constexpr int baz_val = BAZ ;
    virtual int baz() const override { return baz_val ; }
};

template < typename DERIVED > struct monster_impl : virtual monster {

    virtual int foo_bar() const override {
        auto derived = static_cast<const DERIVED*>(this) ;
        return derived->foo_val * 2 + derived->bar() * 3 ;
    }

    virtual int foo_bar_baz() const override {
        auto derived = static_cast<const DERIVED*>(this) ;
        return derived->foo() * 2 + derived->bar_val * 3 + derived->baz_val * 4 ;
    }
};

struct dragon : foo_impl<dragon,5>, bar_impl<dragon,7>, baz_impl<dragon,9>,
                monster_impl<dragon> {};
int main()
{
    const monster& m = dragon{} ;
    std::cout << m.foo_bar_baz() << " - " << m.foo_bar() << " == "
               << dragon::baz_val * 4 << '\n' ;
}

http://coliru.stacked-crooked.com/a/e4dcd4758d171011
Ok, I've revised my entire code using this new system of multiple inheritance and everything is working correctly! Now, I just need to find a better solution to the following problem (of course I'm just showing the skeleton of the diamond problem):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string>

struct Monster {
	std::string name;
	Monster() = default;
	Monster (const std::string& newName): name (newName) {}
	virtual void action() = 0;
	virtual void finalizeMonster (int n) = 0;
	virtual int NumAppearing() const = 0;
};

template <class MONSTER, int N>
struct NumMonsters: virtual public Monster {
	static int numAppearing;
	virtual int NumAppearing() const override {return numAppearing;}
};

template <class MONSTER>
struct MonsterImplement: virtual public Monster {  // CRTP
	MonsterImplement() {finalizeMonster (MONSTER::numAppearing);}  // must be called
	MonsterImplement (int) {}
	virtual void action() override {std::cout << "MonsterImplement::action() called\n";}
	virtual void finalizeMonster (int n) override {}
};

struct Dragon: virtual public Monster {
	int dragonNumber;
	Dragon (int n): dragonNumber (n) {}
	virtual void action() override {std::cout << "Dragon::action() called\n";}
};  // WhiteDragon wants to use this version of action(), not MonsterImplement::action().

class WhiteDragon: public NumMonsters<WhiteDragon, 1>, public MonsterImplement<WhiteDragon>,
 public Dragon {
	public:
		WhiteDragon (const std::string& dragonName, int n): Monster (dragonName), 
MonsterImplement<WhiteDragon>(), Dragon (n) {}
};

int main() {
	WhiteDragon dragon ("White Dragon #5", 5);
	dragon.action();
	std::cin.get();
}

Two things need to be done here. WhiteDragon::action() is to call up Dragon::action() instead of MonsterImplement::action(), and WhiteDragon's constructor needs to call up
finalizeMonster(WhiteDragon::numAppearing).
The above won't compile because of ambiguity. Here is my current solution, which works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <string>

struct Monster {
	std::string name;
	Monster() = default;
	Monster (const std::string& newName): name (newName) {}
	virtual void action() = 0;
	virtual void finalizeMonster (int n) = 0;
	virtual int NumAppearing() const = 0;
};

template <class MONSTER, int N>
struct NumMonsters: virtual public Monster {
	static int numAppearing;
	virtual int NumAppearing() const override {return numAppearing;}
};

template <class MONSTER, int N>
int NumMonsters<MONSTER, N>::numAppearing = N;

template <class MONSTER>
struct MonsterImplement: virtual public Monster {  // CRTP
	MonsterImplement() {finalizeMonster (MONSTER::numAppearing);}
	MonsterImplement (int) {}
	virtual void action() override {std::cout << "MonsterImplement::action() called\n";}
	virtual void finalizeMonster (int n) override {}
};

struct Dragon: virtual public Monster {
	int dragonNumber;
	Dragon (int n): dragonNumber (n) {}
	void dragonAction() {std::cout << "Dragon::action() called\n";}  // here
};

class WhiteDragon: public NumMonsters<WhiteDragon, 1>, public MonsterImplement<WhiteDragon>, 
public Dragon {
	public:
		WhiteDragon (const std::string& dragonName, int n): Monster (dragonName), 
MonsterImplement<WhiteDragon>(), Dragon (n) {}
		virtual void action() override {dragonAction();}  // yuck
};

int main() {
	WhiteDragon dragon ("White Dragon #5", 5);
	dragon.action();
	std::cin.get();
}

Output:
 
Dragon::action() called


Although I only need to define dragonAction once (that's the only good part), the drawback is that now all Dragon subtypes will have to declare
virtual void action() override {dragonAction();}
Not cool (and there are other such overriding virtual functions that I'm not illustrating here).

Here is my attempted solution to avoid this drawback using CRTP. It works with this sample. I will check if it works with my original code (where numAppearing is not just a simple int).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string>

struct Monster {
	std::string name;
	Monster() = default;
	Monster (const std::string& newName): name (newName) {}
	virtual void action() = 0;
	virtual void finalizeMonster (int n) = 0;
	virtual int NumAppearing() const {}
};

template <class MONSTER, int N>
struct MonsterImplement: virtual public Monster {
	static int numAppearing;
	MonsterImplement() {finalizeMonster (MONSTER::numAppearing);}
	virtual int NumAppearing() const override {return numAppearing;}
	virtual void action() override {std::cout << "MonsterImplement::action() called\n";}
	virtual void finalizeMonster (int n) override {}
};

template <class MONSTER, int N>
int MonsterImplement<MONSTER, N>::numAppearing = N;

template <class DERIVED_DRAGON, int N>  // CRTP
struct Dragon: virtual public MonsterImplement<Dragon<DERIVED_DRAGON, N>, N> {
	int dragonNumber;
	Dragon (int n): dragonNumber (n) {
		MonsterImplement<Dragon<DERIVED_DRAGON, N>, N>::finalizeMonster (DERIVED_DRAGON::numAppearing);
	}
	virtual void action() override {std::cout << "Dragon::action() called\n";}
};

class WhiteDragon: public Dragon<WhiteDragon, 4> {
	public:
		WhiteDragon (const std::string& dragonName, int n): Monster (dragonName), Dragon<WhiteDragon, 4> (n) {}
};

int main() {
	WhiteDragon dragon ("White Dragon #3", 3);
	dragon.action();
	std::cout << "WhiteDragon::numAppearing = " << WhiteDragon::numAppearing << std::endl;
	std::cin.get();
}


Output:
1
2
Dragon::action() called
WhiteDragon::numAppearing = 4
Last edited on
Topic archived. No new replies allowed.