array of typdef

I have classes Skeleton, Zombie, Ghoul, ... all derived classes of UndeadMonster. I need to assign indexes to each of them, so
 
typedef Skeleton Undead0; typedef Zombie Undead1; typedef Ghoul Undead2;

though they will compile, will not do the job. What is the correct syntax for what I want to achieve (Undead[0] = Skeleton, Undead[1] = Zombie, etc...)? I need to iterate through all the UndeadMonster subtypes.

This is what I have currently, which works, but I understandibly don't like:
1
2
3
4
5
6
7
8
9
10
11
std::list<Skeleton*> skeletonsPresent;
std::list<Zombie*> zombiesPresent;
// etc...
for (Monster* x: monstersPresent)
{
	if (typeid (*x) == typeid (Skeleton))
		skeletonsPresent.emplace_back (dynamic_cast<Skeleton*>(x));
	else if (typeid (*x) == typeid (Zombie))
		zombiesPresent.emplace_back (dynamic_cast<Zombie*>(x));
	// etc...
}
Last edited on
> What is the correct syntax for what I want to achieve
I don't understand what you want to do.
See the above code. I have to check each and every subtype of UndeadMonster. I want to use a loop to take care of all of them. Ideally something like:
1
2
3
4
5
for (Monster* x: monstersPresent)
    for (int i = 0; i < NUM_UNDEAD_MONSTER_TYPES; i++)
	if (typeid (*x) == typeid (UndeadMonster[i]))
		{undeadMonstersPresent[i].emplace_back (dynamic_cast<UndeadMonster[i]*>(x)); break;}


Or perhaps, I should redefine my UndeadMonster subtypes? Instead of class Skeleton: public UndeadMonster, Zombie: public UndeadMonster, etc..., I should define
1
2
3
4
5
6
template <int N>
class UndeadMonsterType: public UndeadMonster {
	// ...
};

typedef UndeadMonsterType<0> Skeleton; typedef UndeadMonsterType<1> Zombie; typedef UndeadMonsterType<2> Ghoul;  // etc... 

and then use template specialization for each value of N?

But now I run into this problem:
1
2
3
for (Monster* x: monsters)
    for (int N = 0; N < NUM_UNDEAD_MONSTER_TYPES; N++)
       if (typeid (*x) == typeid (UndeadMonsterType<N>))

gives compile error: the value of 'N' is not usable in a constant expression.
So what to do?

I will try using a virtual function
UndeadType<N>::bool isUndeadType (int i) const {return i == N;}
for each template specialization, and see if that helps.
Last edited on
Okay, I've been tinkering around with this for a little while, and I managed to make a program that prints which subclass it is.
I'd like to say that there's probably an easier way of doing it, but I couldn't resist the chance to (hopefully) learn something new about C++11 templates:
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
// I sure hope I'm doing this right...
#include <iostream>
#include <tuple>

struct Base { virtual ~Base() = default; };
// And now for a bunch of derived classes
struct Derived1: Base{};
struct Derived2: Base{};
struct Derived3: Base{};
struct Derived4: Base{};
struct Derived5: Base{};
struct Derived6: Base{};
struct Derived7: Base{};

// Not sure this struct is really necessary, but whatever
struct DerivedIDs
{
    constexpr static int numDeriveds = 7;
    template <int N>
    using type = typename std::tuple_element<N,
              std::tuple<Derived1,Derived2,Derived3,Derived4,
                         Derived5,Derived6,Derived7>>::type;
};

template <int N = 0>
constexpr int getType(Base* b)
{
    return dynamic_cast<DerivedIDs::type<N>*>(b) == nullptr ?
        getType<N+1>(b) : N;
}

template <>
constexpr int getType<DerivedIDs::numDeriveds>(Base*)
{
    return -1;
}

int main()
{
    Derived5 d;
    Base* b = &d;
    
    int type = getType(b);
    std::cout << "Derived" << type+1;
}

If you don't have too many UndeadMonsters, this might be a bit overkill and you may as well just use your standard if-else if statements....

As for actually storing these into the proper container, I'm not quite sure how you have those set up, so I'll just let you try to figure it out yourself. :)

EDIT: Tweaked slightly.
EDIT2: Hey, I didn't really need that separate check function after all...
Last edited on
Wow! Thanks for the insight long double main! I always have a passion for seeing generic solutions using special classes a la JLBorges. I will study your generic method.

It turns out that my idea of template specialization with N and the virtual member functions isUndeadType(int)->bool in each template specialization has worked and solved my original problem:
1
2
3
4
for (Monster* x: monsters)
    for (int i = 0; i < NUM_UNDEAD_MONSTER_TYPES; i++)
        if (x->isUndeadType(i))
            {undeadMonstersPresent[i].emplace_back (dynamic_cast<UndeadMonster*>(x)); break;}

A small bugaboo (not related to my original problem) is that my generalization of the containers
1
2
3
4
std::vector<Skeleton*> skeletonsPresent;  
std::vector<Zombie*> zombiesPresent;  
std::vector<Ghoul*> ghoulsPresent; 
// etc... 

is
std::vector<UndeadMonster*> undeadMonstersPresent[NUM_UNDEAD_MONSTER_TYPES];
which works, but I ideally prefer to have the individual types Skeleton*, Zombie*, Ghoul*, etc... for index 0, 1, 2, ... instead of plain UndeadMonster*. I wonder if there is a way to do this (not that it is necessary for my case, but there might be situtations where that is necessary).
Last edited on
long double main, I went over your code and like your idea of iterating through the tuple of all derived classes. As for whether struct DerivedIDs is necessary or not, I would say shorten the code and just define the tuple globally? The typical dangers of using global variables I don't think is present with this one single tuple. The number of derived classes can be automated using the std::tuple_size class:
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
#include <iostream>
#include <tuple>

struct Base {virtual ~Base() = default;};
struct Derived1: Base{};
struct Derived2: Base{};
struct Derived3: Base{};
struct Derived4: Base{};
struct Derived5: Base{};
struct Derived6: Base{};
struct Derived7: Base{};

#define NOT_FOUND 1
using DerivedsTuple = std::tuple<Derived1, Derived2, Derived3, Derived4, Derived5, Derived6, Derived7>;
template<int N> using type = typename std::tuple_element<N, DerivedsTuple>::type;

template<int N = 0>
constexpr int getDerivedNumber (Base* b) {
    return !dynamic_cast<type<N>*>(b) ? getDerivedNumber<N+1>(b) : N;
}

template<>
constexpr int getDerivedNumber<std::tuple_size<DerivedsTuple>::value> (Base*) {
    return NOT_FOUND;
}

int main() {
    Derived5* d = new Derived5;
    Base* b = d;
    std::cout << "Derived" << getDerivedNumber(b) + 1 << std::endl;
    std::cin.get();
} 


Output (compiled with GCC 4.81):
 
Derived5


But using struct DerivedIDs to wrap those definitions may feel somewhat better too.
Last edited on
This may be all that is required to address the question originally posed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <list>
#include <map>
#include <typeindex>
#include <type_traits>
#include <iostream>

struct UndeadMonster { virtual ~UndeadMonster() = default ; /* ... */ };
struct Skeleton : UndeadMonster { /* ... */ } ;
struct Zombie : UndeadMonster { /* ... */ } ;
struct Ghoul : UndeadMonster { /* ... */ } ;

std::map< std::type_index, std::list<UndeadMonster*> > lists ;

void foo( UndeadMonster* monster )
{ lists[ std::type_index( typeid(*monster) ) ].push_back(monster) ; }

int main()
{
    Zombie z ;
    foo( &z ) ;
    std::cout << lists[ std::type_index( typeid(Zombie) ) ].size() << ' ' 
              << lists[ std::type_index( typeid(Zombie) ) ].front() << ' ' 
              << std::addressof(z) << '\n' ;
}

http://coliru.stacked-crooked.com/a/1be49a8710968c4e

For something more elaborate, a compile time sequence of types is what is usually used.
The basic idea is 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
43
44
45
46
47
48
49
#include <tuple>

template< typename... TYPES > struct type_seq {};

template< typename... TYPES > struct size {} ;
template< typename... TYPES > struct size< type_seq<TYPES...> >
{ enum { value = sizeof...(TYPES) } ; } ;

template< typename... TYPES > struct front {} ;
template< typename T, typename... TYPES > struct front< type_seq< T, TYPES... > >
{ using type = T ; };

template< typename T, typename... TYPES > struct push_front {} ;
template< typename T, typename... TYPES > struct push_front< T, type_seq<TYPES...> >
{ using type = type_seq< T, TYPES... >; } ;

template< typename... TYPES > struct pop_front {} ;
template< typename T, typename... TYPES > struct pop_front< type_seq< T, TYPES... > >
{ using type = type_seq<TYPES...> ; } ;

template< unsigned int N, typename... TYPES > struct at {} ;
template< unsigned int N, typename... TYPES > struct at< N, type_seq<TYPES...> >
{ using type = typename std::tuple_element< N, std::tuple<TYPES...> >::type ; };

template< typename T, typename U, int N = 0 > struct index_of ;
template< typename T, int N > struct index_of< T, type_seq<>, N > { enum { value = -1 } ; };
template< typename T, typename FIRST, typename ...REST, int N >
struct index_of< T, type_seq<FIRST,REST...>, N >
          : std::conditional< std::is_same<T,FIRST>::value,
                                std::integral_constant<std::size_t,N>,
                                index_of< T, type_seq<REST...>, N+1 > >::type {} ;
// etc.

struct UndeadMonster { virtual ~UndeadMonster() = default ; /* ... */ };
struct Skeleton : UndeadMonster { /* ... */ } ;
struct Zombie : UndeadMonster { /* ... */ } ;
struct Ghoul : UndeadMonster { /* ... */ } ;

using undead_types = type_seq< Skeleton, Zombie, Ghoul > ;

int main()
{
    static_assert( std::is_same< at<1,undead_types>::type, Zombie >::value, "not ok" ) ;
    
    static_assert( index_of< Ghoul, undead_types >::value == 2, "not ok" ) ;

    using T = push_front< double, push_front< int, undead_types >::type >::type ;
    static_assert( index_of< Ghoul, T >::value == 4, "not ok" ) ;
}

http://coliru.stacked-crooked.com/a/204a8e1531bb14fd

In practice, one would use Boost MPL.
http://www.boost.org/doc/libs/1_55_0/libs/mpl/doc/refmanual/classes.html
http://www.boost.org/doc/libs/1_55_0/libs/mpl/doc/refmanual/for-each.html
I guess I was not clear with my original question, but the separate lists need to be identified with integers too (not just the UndeadMonsters subtypes) so that I can check through all the lists with a loop. That I was not doing this in my original post was my way of showing what the problem was. What I have now, which is doing what I want to do, is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for (Monster* x: monsters)
    for (int i = 0; i < NUM_UNDEAD_MONSTER_TYPES; i++)
		if (x->isUndeadType(i))
		{
			undeadMonstersPresent[i].emplace_back (dynamic_cast<UndeadMonster*>(x));
			break;
		}
//... Then later I have
	for (int i = 0; i < NUM_UNDEAD_MONSTER_TYPES; i++)
	{
		if (undeadMonstersPresent[i].empty() || (roll < turnUndeadTable[row][i]))
			continue;
		const int hitDice = static_cast<int> (undeadMonstersPresent[i].front()->HitDice().level);
		const std::size_t num = undeadMonstersPresent[i].size();
  // etc...


But your second general solution looks truly up there! I will study it now, and maybe I can get ideas on how to improve what I already have.

My key line
if (x->isUndeadType(i))
required me to make template specializations for all my UndeadMonster subtypes, whereas long double main's idea does not require that messiness. Hence I have now changed it to
if ((UndeadMonsterID(x) == i)
using his way. But I have a feeling I can extract an even better method once I've studied your second more general solution. Your line
using undead_types = type_seq< Skeleton, Zombie, Ghoul >
suggests that your solution is a generalization of his.
Last edited on
> but the separate lists need to be identified with integers too (not just the UndeadMonsters subtypes)
> so that I can check through all the lists with a loop.

Why are these integers a necessity? We can iterate through all the lists without them, can't we?

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
#include <list>
#include <map>
#include <typeindex>
#include <type_traits>
#include <iostream>
#include <iomanip>

struct UndeadMonster { virtual ~UndeadMonster() = default ; /* ... */ };
struct Skeleton : UndeadMonster { /* ... */ } ;
struct Zombie : UndeadMonster { /* ... */ } ;
struct Ghoul : UndeadMonster { /* ... */ } ;

std::map< std::type_index, std::list<UndeadMonster*> > lists ;

void foo( UndeadMonster* monster )
{ lists[ std::type_index( typeid(*monster) ) ].push_back(monster) ; }

int main()
{
    UndeadMonster* monsters[] = 
    { 
        new Zombie, new Skeleton, new Ghoul, new Skeleton, new Zombie, new Ghoul, 
        new Zombie, new Ghoul, new Zombie, new Skeleton, new Ghoul, new Zombie 
    } ;
    for( auto ptr : monsters ) foo(ptr) ;
    
    // iteratoe through the lists
    std::cout << "lists\n--------------\n" ;
    for( const auto& pair : lists )
    {
        std::cout << std::setw(12) << pair.first.name() << "  -  " ;
        for( auto p : pair.second ) std::cout << p << "  " ;
        std::cout << '\n' ;
    }
}

http://coliru.stacked-crooked.com/a/d7ecea26a5aef430
Actually, I still needed the integers because there is a look-up table where column 0 is for Skeleton, column 1 for Zombie, etc... But you're method is order-independent, and hence better, so I persisted with your method and revised my entire function and it works without problems. However, just wondering why the two const qualifiers below will not compile (whereas they would with my former method):

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
inline bool Cleric::turnsAwayUndead (const std::vector<Monster*>& monsters) const {
  enum {NoTurn = 13, Turned = 2, Dispelled = 1};
  static /*const*/ std::map<std::type_index, std::array<int, 11>> turnUndeadValues = {  // const will not compile (why?)
    {std::type_index (typeid (Skeleton)), {     7, Turned, Turned, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled} },
    {std::type_index (typeid (Zombie)),   {     9,      7, Turned,    Turned, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled} },
    {std::type_index (typeid (Ghoul)),    {    11,      9,      7,    Turned,    Turned, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled, Dispelled} }
    // etc...
  };
  static /*const*/ std::map<std::type_index, std::string> undeadMonsterName {  // const will not compile (why?)
    {std::type_index (typeid (Skeleton)), "skeleton"}, 
    {std::type_index (typeid (Zombie)), "zombie"}, 
    {std::type_index (typeid (Ghoul)), "ghoul"}
    // etc...
  };
  std::map<std::type_index, std::deque<UndeadMonster*>> activeUndeadMonstersPresentMap;
  for (Monster* x: monsters)
    if (x->isUndead() && !x->incapacitated())
      activeUndeadMonstersPresentMap[std::type_index (typeid (*x))].emplace_back (dynamic_cast<UndeadMonster*>(x));
  using td = std::pair<std::type_index, std::deque<UndeadMonster*>>;
  std::vector<td> activeUndeadMonstersPresent;  // Need to sort all the elements of activeUndeadMonstersPresentMap according to hitDice of the undead monsters in the deques.
  std::copy (activeUndeadMonstersPresentMap.begin(), activeUndeadMonstersPresentMap.end(), std::back_inserter (activeUndeadMonstersPresent));
  std::sort (activeUndeadMonstersPresent.begin(), activeUndeadMonstersPresent.end(), 
    [](const td& x, const td& y)->bool {return x.second.front()->HitDice() < y.second.front()->HitDice();});
  const int roll = (std::rand() % 6 + 1) + (std::rand() % 6 + 1),   index = (Level() <= 10) ? Level() - 1 : 10;
  if (roll < turnUndeadValues[activeUndeadMonstersPresent.front().first][index])  // why the /*const*/ will cause this line to fail?
  {
    pressContinue (Name() + " has failed to turn away any of the undead monsters.");
    return false;
  }
  int numHitDiceAffected = (std::rand() % 6 + 1) + (std::rand() % 6 + 1);
  for (td& pair: activeUndeadMonstersPresent)
  {
    if (roll < turnUndeadValues[pair.first][index])
      continue;
    const int hitDice = static_cast<int> (pair.second.front()->HitDice().level);
    const std::size_t num = pair.second.size();
    int totalNumHitDice = num * hitDice;
    if (numHitDiceAffected >= totalNumHitDice)
    {
      const std::string nameOfUndeadMonster = undeadMonsterName[pair.first];  // why the /*const*/ will cause this line to fail?
      std::cout << "All " << num << " active " << nameOfUndeadMonster << "s have been turned away." << std::endl;
      for (UndeadMonster* x: pair.second)
        x->isTurnedAway();
    }
	// etc...
  }
}

The error message when I put back in the const in undeadMonsterName is:
error: passing 'const std::map<std::type_in
dex, std::basic_string<char> >' as 'this' argument of 'std::map<_Key, _Tp,
_Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::oper
ator[](const key_type&) [with _Key = std::type_index; _Tp = std::basic_stri
ng<char>; _Compare = std::less<std::type_index>; _Alloc = std::allocator<st
d::pair<const std::type_index, std::basic_string<char> > >; std::map<_Key,
_Tp, _Compare, _Alloc>::mapped_type = std::basic_string<char>; std::map<_Ke
y, _Tp, _Compare, _Alloc>::key_type = std::type_index]' discards qualifiers
[-fpermissive]
const std::string nameOfUndeadMonster = undeadMonsterName[pair.first];
Last edited on
std::map<>::operator[] inserts a key default-initialized-value pair if the key does not exist; so it requires a modifiable map. With a const map<>, use find()

1
2
3
4
5
6
7
8
9
// const std::string nameOfUndeadMonster = undeadMonsterName[pair.first];
auto iter = undeadMonsterName.find( pair.first ) ;
if( iter != undeadMonsterName.end() ) // found it
{
     const std::string nameOfUndeadMonster = iter->second ;

     // use nameOfUndeadMonster 

}



Topic archived. No new replies allowed.